如何在应用加载/数据库操作时显示加载表单及多窗体调用实现
多窗体按需调用加载弹窗的实现方案
需求:应用启动或从数据库获取大量数据时显示加载表单,实现数据库操作时的加载逻辑。已编写基于BackgroundWorker的代码,需要指导如何在多个窗体中按需调用
showLoading和closeLoading方法。
现有代码
Private bgw As New BackgroundWorker Public Sub showLoading() bgw.WorkerSupportsCancellation = True AddHandler bgw.DoWork, AddressOf bgw_doWork AddHandler bgw.RunWorkerCompleted, AddressOf bgw_Complete If Not bgw.IsBusy = True Then bgw.RunWorkerAsync() End If End Sub Public Sub closeLoading() If bgw.WorkerSupportsCancellation = True Then bgw.CancelAsync() End If End Sub Private Sub bgw_doWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) Dim loadingScreen As New frmLoading loadingScreen.Show() While True If Not bgw.CancellationPending Then Threading.Thread.Sleep(50) Else e.Cancel = True Exit While End If End While End Sub Private Sub bgw_Complete(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Dim lstFrm As New List(Of frmLoading) lstFrm = Application.OpenForms.OfType(Of frmLoading)() If lstFrm.Count > 0 Then For Each frm As frmLoading In lstFrm frm.Close() Next End If End Sub
解决方案
一、修复原代码的核心问题
- 跨线程UI操作错误:
bgw_doWork运行在后台线程,直接调用loadingScreen.Show()会触发跨线程异常,必须通过UI线程调度窗体显示。 - 重复绑定事件:每次调用
showLoading()都会重复添加事件处理,导致回调逻辑多次执行,应在初始化时仅绑定一次。 - 实例复用冲突:单个
BackgroundWorker实例重复调用可能出现状态混乱,建议每次显示加载时创建新实例或严格校验状态。
二、封装公共加载管理器
将加载逻辑封装为独立公共类,让所有窗体可直接调用,避免重复代码:
Imports System.ComponentModel Imports System.Threading Public Class LoadingManager Private Shared bgw As BackgroundWorker Public Shared Sub ShowLoading() If bgw IsNot Nothing AndAlso bgw.IsBusy Then Return ' 避免重复显示加载窗体 End If bgw = New BackgroundWorker() bgw.WorkerSupportsCancellation = True ' 仅绑定一次事件 AddHandler bgw.DoWork, AddressOf Bgw_DoWork AddHandler bgw.RunWorkerCompleted, AddressOf Bgw_RunWorkerCompleted bgw.RunWorkerAsync() End Sub Public Shared Sub CloseLoading() If bgw IsNot Nothing AndAlso bgw.WorkerSupportsCancellation AndAlso bgw.IsBusy Then bgw.CancelAsync() End If End Sub Private Shared Sub Bgw_DoWork(sender As Object, e As DoWorkEventArgs) Dim worker = DirectCast(sender, BackgroundWorker) Dim loadingForm As frmLoading = Nothing ' 通过UI线程创建并显示加载窗体 Application.OpenForms(0).Invoke(Sub() loadingForm = New frmLoading() loadingForm.Show() End Sub) ' 循环等待取消信号 While Not worker.CancellationPending Thread.Sleep(50) End While e.Cancel = True End Sub Private Shared Sub Bgw_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) ' 关闭所有加载窗体并释放资源 Dim loadingForms = Application.OpenForms.OfType(Of frmLoading).ToList() For Each frm In loadingForms If Not frm.IsDisposed Then frm.Close() frm.Dispose() End If Next ' 清理事件绑定与实例 RemoveHandler bgw.DoWork, AddressOf Bgw_DoWork RemoveHandler bgw.RunWorkerCompleted, AddressOf Bgw_RunWorkerCompleted bgw = Nothing End Sub End Class
三、多窗体调用示例
示例1:窗体启动时加载数据
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 显示加载窗体 LoadingManager.ShowLoading() ' 后台执行数据库初始化逻辑 Task.Run(Sub() ' 替换为实际数据库操作 Thread.Sleep(3000) ' 操作完成后关闭加载 LoadingManager.CloseLoading() ' 回到UI线程更新界面 Me.Invoke(Sub() Me.lblStatus.Text = "数据初始化完成" End Sub) End Sub) End Sub
示例2:按钮触发大数据查询
Private Sub btnQuery_Click(sender As Object, e As EventArgs) Handles btnQuery.Click LoadingManager.ShowLoading() Task.Run(Sub() ' 执行大量数据库查询 Dim data = FetchLargeDataFromDB() ' 查询完成关闭加载 LoadingManager.CloseLoading() ' 更新DataGridView(UI线程操作) Me.Invoke(Sub() dgvData.DataSource = data End Sub) End Sub) End Sub ' 模拟数据库查询方法 Private Function FetchLargeDataFromDB() As DataTable Thread.Sleep(2000) Dim dt As New DataTable() dt.Columns.Add("ID") dt.Columns.Add("Name") For i = 1 To 1000 dt.Rows.Add(i, $"数据项 {i}") Next Return dt End Function
四、关键注意事项
- UI线程安全:所有操作UI的代码必须通过
Invoke/BeginInvoke回到主线程,避免跨线程异常。 - 防止重复调用:调用
ShowLoading()前检查是否已有加载窗体显示,避免重复创建。 - 资源清理:关闭加载窗体后调用
Dispose()释放资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Nikunj Chovatiya
相关产品推荐
相关产品推荐

