VB.NET-使用后台工作程序填充DataGridView而不冻结表单
我的程序包含一个部分,用户可以通过单击按钮用数据库中的内容填充DataGridView。我通过使用后台工作程序来实现这一点,总而言之,我的代码似乎工作得很好,但我不确定我是否按照预期的方式实现了后台工作程序。 问题是,尽管使用了RunWorkerAsync()方法,我的整个表单仍然会冻结10秒钟左右,这大约是处理数据和向DataGridView添加新行所需的时间。我想要实现的是一个解决方案,其中表单保持功能,进度显示在进度条中 以下是我目前正在做的一些(伪)代码:VB.NET-使用后台工作程序填充DataGridView而不冻结表单,vb.net,winforms,asynchronous,datagridview,backgroundworker,Vb.net,Winforms,Asynchronous,Datagridview,Backgroundworker,我的程序包含一个部分,用户可以通过单击按钮用数据库中的内容填充DataGridView。我通过使用后台工作程序来实现这一点,总而言之,我的代码似乎工作得很好,但我不确定我是否按照预期的方式实现了后台工作程序。 问题是,尽管使用了RunWorkerAsync()方法,我的整个表单仍然会冻结10秒钟左右,这大约是处理数据和向DataGridView添加新行所需的时间。我想要实现的是一个解决方案,其中表单保持功能,进度显示在进度条中 以下是我目前正在做的一些(伪)代码: private myList
private myList as new List(of Object)
Private Sub btnClick(sender As Object, e As EventArgs) Handles myButton.Click
myBgWorker.RunWorkerAsync()
End Sub
Private Sub doWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles myBgWorker.DoWork
execute some SQL()
for each SQL-result row
myList.add(rowData)
myBgWorker.ReportProgress()
Loop
End Sub
Private Sub reportProgress(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles myBgWorker.ProgressChanged
pgrogressBar.Value = e.ProgressPercentage
End Sub
Private Sub done(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles myBgWorker.RunWorkerCompleted
For Each entry in myList
Dim rowIdx As Integer = myDGV.Rows.Add()
Dim dgvRow As DataGridViewRow = myDGV.Rows(rowIdx)
dgvRow.Cells(0).Value = "something"
dgvRow.Cells(1).Value = "something else"
dgvRow.Cells(2).Value = "even more..."
Next
MsgBox("I'm done.")
End Sub
因此,基本上工人执行一些SQL,循环接收到的行并填充列表,同时在进度条中表示进度。到现在为止,一直都还不错。但一旦它执行RunWorkerCompleted代码,表单就会冻结,我必须等待所有行都添加到DataGridView
我还尝试在reportProgress方法中分别添加每一行,但结果是相同的,而且我理解这些后台工作人员的方式是相同的,此时您不需要更新UI。您是对的,您没有正确使用BackgroundWorker。您应该知道在哪个线程上调用了哪些事件 您有两个线程:负责所有更新的UI线程和负责所有冗长处理的BackgroundWorker线程 后台工作人员的标准用法 (1) UI线程创建类BackGroundWorker的对象,并设置一些属性。最重要的是:WorkerReportsProgress和WorkerSupportsScanCellation (2) UI类向BackgroundWorker添加了三个事件处理程序:
附加将执行后台工作的方法。此工作将由BackgroundWorker线程完成BackGroundWorker.DoWork
。附加更新UI的方法,以通知操作员进度。此方法将由UI线程执行BackGroundWorker.ProgressChanged
。将BackgroundWorker完成时必须执行的方法附加到DoWork结束时。此方法也将由UI线程执行BackGroundWorker.runworker已完成
this.backgroundWorker.RunWorkerAsync();
如果需要传递参数,请执行以下操作:
MyParam myParam = new MyParam() {...}
this.backgroundWorker.RunWorkerAsync(myParam);
将创建一个新线程,它将开始执行附加到eventBackGroundWorker.DoWork的方法
Sub doWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
Handles myBgWorker.DoWork
{
BackgroundWorker backgroundWorker = sender as BackgroundWorkder
do some work that doesn't take too long, because you want to report progress
while work not finished and not backGroundWorker.CancellationRequested
{
create a progress report, put it in an object
if possible: calculate an estimation about your progress in %;
// report progress, don't use variable backgroundWorkder, use the sender
(sender as BackgroundWorker).ReportProgress(sender, progressPercentage, progressReport);
}
// if here: finished with your calculations, create a result report:
MyResultReport myReport = ...
e.Result = myReport;
// if you promised to support cancellation or errors:
e.Error = ...
e.Cancelled = backgroundWorker.CancellationRequested;
e.State = ...
End Sub
如果你想报告进度,你必须在进度中放入一些数据,否则你的进度接收者对你的进度没有任何线索
您可以使用ProgressBar或类似的百分比。progressReport可用于显示UI线程希望了解的其他信息
顺便说一下:如果您的backgroundWorker在其过程之外不使用任何变量,这是明智的。使用发件人获取BackGroundWorker。将backgroundWorker需要的所有值作为输入参数传递。这样,您的后台工作人员就可以完全不知道是谁发起的。这使得重用它更容易,更改工作线程的所有者也更容易,因此单元测试也更容易
考虑通过创建一个单独的类来完成后台工作,从而强制实现这种分离。这几乎不是更多的工作。如果可重用性、单元测试和可维护性对您不重要,则无需这样做
当backgroundWorker完成繁重的计算后,您可以将计算结果放入e.result中
每次BackgroundWorker报告进度时,UI线程都会在附加到ProgressChanged
void ProgressReported(object sender, ProgressChangedEventArgs e)
{
// the designer of the worker thread promised to put his progress in a class MyProgressReport:
int progressPercentage = e.progressPercentage;
MyProgressReport progressReport = e.UserState as MyProgressReport;
this.ProgressReported(progressPercentage, progressReport);
}
顺便说一下:如果您计划对几个后台工作人员使用此事件处理程序,您应该检查发送者
,以确定谁报告进度以及如何处理进度报告
void ProgressReported(int progressPercentage, MyProgressReport progressReport)
{
this.ProgressBar1.Value = progressPercentage;
this.ProgressTextBox.Text = progressReport.ToString();
}
最后,如果工作线程已完成繁重的处理,则调用RunworkerCompleted的EventHandler。UI线程将处理它:
你应该告诉后台工作人员的设计师他应该在结果中加入什么。它可能会删除从数据库中获取的所有数据
public RunWorkerReportsCompletion(object sender, RunWorkderCompletedEventArgs e)
{
MyResultReport result = e.Result as MyResultReport;
ProcessResult(result);
}
如果您预期有问题或取消,请考虑检查属性<代码>取消< /代码>和<代码>错误< /代码> .<
总结如下:
- 在
DoWork中执行所有冗长的处理
- ProgressChanged和RunWorkerCompleted由UI线程执行。它们应该很轻
- BackgroundWorker不应使用表单中的任何值;使用事件中的参数进行通信
- 考虑支持取消。如果窗体正在关闭,而工作线程仍在工作,则可以取消并等待后台工作线程完成
“一旦执行RunWorkerCompleted代码,表单就会冻结”。当然会,因为事件处理程序是在UI线程上执行的。这就是重点。您别无选择,因为必须在UI线程上修改UI。当然,您应该将列表绑定到网格,而不是手动添加行。这将更快,尽管可能仍会暂停。这是不可避免的。你最大的问题是你正在使用一个列表(对象)
手动向DGV添加行。这并不是使用该控件的真正方式。您可以使用类对象来描述数据并使用BindingList作为容器,然后将其设置为DGV的数据源,或者填充数据表并将其设置为数据源