如果你有一个需要很长时间才能完成的作,并且不希望用户界面(UI)停止响应或阻止,则可以使用该 BackgroundWorker 类在另一个线程上执行该作。
本演练演示了如何使用 BackgroundWorker 类在“后台”执行耗时的计算,而用户界面仍保持响应。 完成时,你将有一个以异步方式计算 Fibonacci 数字的应用程序。 尽管计算较大的 Fibonacci 数可能需要一定的时间,但主 UI 线程不会因这种延迟而中断,并且表单在计算过程中会响应。
本演练涉及以下任务:
创建基于 Windows 的应用程序
在您的窗体中创建BackgroundWorker
添加异步事件处理程序
添加进度报告功能并支持取消操作
有关此示例中使用的代码的完整列表,请参阅 如何:实现使用后台操作的窗体。
创建使用后台操作的表单
在 Visual Studio 中,创建一个名为(文件>新建>项目>Visual C# 或 Visual Basic>经典桌面>Windows 窗体应用程序)的应用程序项目
BackgroundWorkerExample
。在 解决方案资源管理器中,右键单击 Form1 ,然后从快捷菜单中选择“ 重命名 ”。 将文件名更改为
FibonacciCalculator
。 当系统询问是否要重命名对代码元素Form1
的所有引用时,请单击是按钮。将NumericUpDown控件从工具箱拖到窗体上。 将 Minimum 属性设置为
1
,将 Maximum 属性设置为91
。向窗体添加两个 Button 控件。
重命名第一个Button控件为
startAsyncButton
,并将Text属性设置为Start Async
。 重命名第二 Button 个控件cancelAsyncButton
,并将属性设置为 TextCancel Async
。 将其 Enabled 属性设置为false
。为这两个 Button 控件 Click 的事件创建事件处理程序。 有关详细信息,请参阅 如何:使用设计器创建事件处理程序。
将 Label 控件从 工具箱 拖到窗体上,并将其重命名
resultLabel
。将ProgressBar控件从工具箱拖到窗体上。
使用设计器创建 BackgroundWorker
可以使用 Windows窗体设计器为您的异步操作创建BackgroundWorker。
在工具箱的组件选项卡中,将BackgroundWorker拖到窗体上。
添加异步事件处理程序
现在可以为 BackgroundWorker 组件的异步事件添加事件处理程序。 将在后台运行的耗时操作(计算 Fibonacci 数字)被其中一个事件处理程序调用。
在 “属性” 窗口中,选中 BackgroundWorker 组件后,单击“ 事件 ”按钮。 双击 DoWork 和 RunWorkerCompleted 事件以创建事件处理程序。 有关如何使用事件处理程序的详细信息,请参阅 如何:使用设计器创建事件处理程序。
在表单中创建名为的新方法
ComputeFibonacci
。 此方法执行实际工作,并将在后台运行。 此代码演示 Fibonacci 算法的递归实现,该算法明显效率低下,需要更长的时间才能完成较大的数字。 此处用于说明性目的,以展示可能导致应用程序中长时间延迟的操作。// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci( int n, BackgroundWorker^ worker, DoWorkEventArgs ^ e ) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ( (n < 0) || (n > 91) ) { throw gcnew ArgumentException( "value must be >= 0 and <= 91","n" ); } long result = 0; // Abort the operation if the user has cancelled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if ( worker->CancellationPending ) { e->Cancel = true; } else { if ( n < 2 ) { result = 1; } else { result = ComputeFibonacci( n - 1, worker, e ) + ComputeFibonacci( n - 2, worker, e ); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); } } return result; }
// This is the method that does the actual work. For this // example, it computes a Fibonacci number and // reports progress as it does its work. long ComputeFibonacci(int n, BackgroundWorker worker, DoWorkEventArgs e) { // The parameter n must be >= 0 and <= 91. // Fib(n), with n > 91, overflows a long. if ((n < 0) || (n > 91)) { throw new ArgumentException( "value must be >= 0 and <= 91", "n"); } long result = 0; // Abort the operation if the user has canceled. // Note that a call to CancelAsync may have set // CancellationPending to true just after the // last invocation of this method exits, so this // code will not have the opportunity to set the // DoWorkEventArgs.Cancel flag to true. This means // that RunWorkerCompletedEventArgs.Cancelled will // not be set to true in your RunWorkerCompleted // event handler. This is a race condition. if (worker.CancellationPending) { e.Cancel = true; } else { if (n < 2) { result = 1; } else { result = ComputeFibonacci(n - 1, worker, e) + ComputeFibonacci(n - 2, worker, e); } // Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); } } return result; }
' This is the method that does the actual work. For this ' example, it computes a Fibonacci number and ' reports progress as it does its work. Function ComputeFibonacci( _ ByVal n As Integer, _ ByVal worker As BackgroundWorker, _ ByVal e As DoWorkEventArgs) As Long ' The parameter n must be >= 0 and <= 91. ' Fib(n), with n > 91, overflows a long. If n < 0 OrElse n > 91 Then Throw New ArgumentException( _ "value must be >= 0 and <= 91", "n") End If Dim result As Long = 0 ' Abort the operation if the user has canceled. ' Note that a call to CancelAsync may have set ' CancellationPending to true just after the ' last invocation of this method exits, so this ' code will not have the opportunity to set the ' DoWorkEventArgs.Cancel flag to true. This means ' that RunWorkerCompletedEventArgs.Cancelled will ' not be set to true in your RunWorkerCompleted ' event handler. This is a race condition. If worker.CancellationPending Then e.Cancel = True Else If n < 2 Then result = 1 Else result = ComputeFibonacci(n - 1, worker, e) + _ ComputeFibonacci(n - 2, worker, e) End If ' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If End If Return result End Function
在 DoWork 事件处理程序中,添加对
ComputeFibonacci
方法的调用。 从DoWorkEventArgs的Argument属性中获取第一个ComputeFibonacci
参数。 稍后将使用参数 BackgroundWorker 和 DoWorkEventArgs 来进行进度报告和取消支持。 将ComputeFibonacci
的返回值赋给DoWorkEventArgs的Result属性。 此结果将可用于 RunWorkerCompleted 事件处理程序。注释
DoWork事件处理程序不直接引用
backgroundWorker1
实例变量,因为这样会将此事件处理程序与特定实例BackgroundWorker相耦合。 而是从sender
参数中恢复对引发此事件的引用BackgroundWorker。 当表单承载多个 BackgroundWorker表单时,这一点很重要。 在DoWork事件处理程序中,不操作任何用户界面对象也很重要。 而是通过 BackgroundWorker 事件与用户界面通信。// This event handler is where the actual, // potentially time-consuming work is done. void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e ) { // Get the BackgroundWorker that raised this event. BackgroundWorker^ worker = dynamic_cast<BackgroundWorker^>(sender); // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e->Result = ComputeFibonacci( safe_cast<Int32>(e->Argument), worker, e ); }
// This event handler is where the actual, // potentially time-consuming work is done. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // Get the BackgroundWorker that raised this event. BackgroundWorker worker = sender as BackgroundWorker; // Assign the result of the computation // to the Result property of the DoWorkEventArgs // object. This is will be available to the // RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci((int)e.Argument, worker, e); }
' This event handler is where the actual work is done. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As DoWorkEventArgs) _ Handles backgroundWorker1.DoWork ' Get the BackgroundWorker object that raised this event. Dim worker As BackgroundWorker = _ CType(sender, BackgroundWorker) ' Assign the result of the computation ' to the Result property of the DoWorkEventArgs ' object. This is will be available to the ' RunWorkerCompleted eventhandler. e.Result = ComputeFibonacci(e.Argument, worker, e) End Sub
在
startAsyncButton
控件的 Click 事件处理程序中,添加启动异步作的代码。void startAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Reset the text in the result label. resultLabel->Text = String::Empty; // Disable the UpDown control until // the asynchronous operation is done. this->numericUpDown1->Enabled = false; // Disable the Start button until // the asynchronous operation is done. this->startAsyncButton->Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this->cancelAsyncButton->Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1->Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1->RunWorkerAsync( numberToCompute ); }
private void startAsyncButton_Click(System.Object sender, System.EventArgs e) { // Reset the text in the result label. resultLabel.Text = String.Empty; // Disable the UpDown control until // the asynchronous operation is done. this.numericUpDown1.Enabled = false; // Disable the Start button until // the asynchronous operation is done. this.startAsyncButton.Enabled = false; // Enable the Cancel button while // the asynchronous operation runs. this.cancelAsyncButton.Enabled = true; // Get the value from the UpDown control. numberToCompute = (int)numericUpDown1.Value; // Reset the variable for percentage tracking. highestPercentageReached = 0; // Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute); }
Private Sub startAsyncButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles startAsyncButton.Click ' Reset the text in the result label. resultLabel.Text = [String].Empty ' Disable the UpDown control until ' the asynchronous operation is done. Me.numericUpDown1.Enabled = False ' Disable the Start button until ' the asynchronous operation is done. Me.startAsyncButton.Enabled = False ' Enable the Cancel button while ' the asynchronous operation runs. Me.cancelAsyncButton.Enabled = True ' Get the value from the UpDown control. numberToCompute = CInt(numericUpDown1.Value) ' Reset the variable for percentage tracking. highestPercentageReached = 0 ' Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(numberToCompute) End Sub
在事件处理程序中 RunWorkerCompleted ,将计算结果分配给
resultLabel
控件。// This event handler deals with the results of the // background operation. void backgroundWorker1_RunWorkerCompleted( Object^ /*sender*/, RunWorkerCompletedEventArgs^ e ) { // First, handle the case where an exception was thrown. if ( e->Error != nullptr ) { MessageBox::Show( e->Error->Message ); } else if ( e->Cancelled ) { // Next, handle the case where the user cancelled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel->Text = "Cancelled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel->Text = e->Result->ToString(); } // Enable the UpDown control. this->numericUpDown1->Enabled = true; // Enable the Start button. startAsyncButton->Enabled = true; // Disable the Cancel button. cancelAsyncButton->Enabled = false; }
// This event handler deals with the results of the // background operation. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { // First, handle the case where an exception was thrown. if (e.Error != null) { MessageBox.Show(e.Error.Message); } else if (e.Cancelled) { // Next, handle the case where the user canceled // the operation. // Note that due to a race condition in // the DoWork event handler, the Cancelled // flag may not have been set, even though // CancelAsync was called. resultLabel.Text = "Canceled"; } else { // Finally, handle the case where the operation // succeeded. resultLabel.Text = e.Result.ToString(); } // Enable the UpDown control. this.numericUpDown1.Enabled = true; // Enable the Start button. startAsyncButton.Enabled = true; // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
' This event handler deals with the results of the ' background operation. Private Sub backgroundWorker1_RunWorkerCompleted( _ ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _ Handles backgroundWorker1.RunWorkerCompleted ' First, handle the case where an exception was thrown. If (e.Error IsNot Nothing) Then MessageBox.Show(e.Error.Message) ElseIf e.Cancelled Then ' Next, handle the case where the user canceled the ' operation. ' Note that due to a race condition in ' the DoWork event handler, the Cancelled ' flag may not have been set, even though ' CancelAsync was called. resultLabel.Text = "Canceled" Else ' Finally, handle the case where the operation succeeded. resultLabel.Text = e.Result.ToString() End If ' Enable the UpDown control. Me.numericUpDown1.Enabled = True ' Enable the Start button. startAsyncButton.Enabled = True ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub
增加进度报告和取消功能支持
对于将需要很长时间的异步操作,通常希望向用户报告进展,并允许用户取消操作。 该 BackgroundWorker 类提供一个事件,用于在你的后台操作进行时发布进度。 它还提供了一个标志,允许工作线程代码检测对 CancelAsync 的调用并自我中断。
实现进度报告
在 “属性”窗口中,选择
backgroundWorker1
。 将 WorkerReportsProgress 和 WorkerSupportsCancellation 属性设置为true
.在
FibonacciCalculator
格式中声明两个变量。 这些内容将用于跟踪进度。int numberToCompute; int highestPercentageReached;
private int numberToCompute = 0; private int highestPercentageReached = 0;
Private numberToCompute As Integer = 0 Private highestPercentageReached As Integer = 0
为ProgressChanged事件添加事件处理程序。 在ProgressChanged事件处理程序中,用ProgressChangedEventArgs参数的ProgressPercentage属性更新ProgressBar。
// This event handler updates the progress bar. void backgroundWorker1_ProgressChanged( Object^ /*sender*/, ProgressChangedEventArgs^ e ) { this->progressBar1->Value = e->ProgressPercentage; }
// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }
' This event handler updates the progress bar. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.progressBar1.Value = e.ProgressPercentage End Sub
实现对取消的支持
在
cancelAsyncButton
控件的 Click 事件处理程序中,添加取消异步作的代码。void cancelAsyncButton_Click( System::Object^ /*sender*/, System::EventArgs^ /*e*/ ) { // Cancel the asynchronous operation. this->backgroundWorker1->CancelAsync(); // Disable the Cancel button. cancelAsyncButton->Enabled = false; }
private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
Private Sub cancelAsyncButton_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cancelAsyncButton.Click ' Cancel the asynchronous operation. Me.backgroundWorker1.CancelAsync() ' Disable the Cancel button. cancelAsyncButton.Enabled = False End Sub
方法中的
ComputeFibonacci
代码片段报告进度并支持取消操作。if ( worker->CancellationPending ) { e->Cancel = true; }
if (worker.CancellationPending) { e.Cancel = true; }
If worker.CancellationPending Then e.Cancel = True
// Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if ( percentComplete > highestPercentageReached ) { highestPercentageReached = percentComplete; worker->ReportProgress( percentComplete ); }
// Report progress as a percentage of the total task. int percentComplete = (int)((float)n / (float)numberToCompute * 100); if (percentComplete > highestPercentageReached) { highestPercentageReached = percentComplete; worker.ReportProgress(percentComplete); }
' Report progress as a percentage of the total task. Dim percentComplete As Integer = _ CSng(n) / CSng(numberToCompute) * 100 If percentComplete > highestPercentageReached Then highestPercentageReached = percentComplete worker.ReportProgress(percentComplete) End If
检查站
此时,可以编译并运行 Fibonacci Calculator 应用程序。
按 F5 编译并运行应用程序。
在后台运行计算时,你将看到 ProgressBar 显示计算的进度直至完成。 还可以取消挂起的操作。
对于小数,计算应非常快,但对于较大的数字,应会看到明显的延迟。 如果输入的值为 30 或更大,应该会看到几秒钟的延迟,具体取决于计算机的速度。 对于大于 40 的值,完成计算可能需要几分钟或数小时。 虽然计算器正忙于计算大型 Fibonacci 数字,但请注意,你可以自由移动窗体,最小化、最大化甚至消除它。 这是因为主 UI 线程未等待计算完成。
后续步骤
现在,你已经实现了使用 BackgroundWorker 组件来在后台执行计算的窗体,可以探索异步操作的其他可能性。
使用多个 BackgroundWorker 对象进行多项同时操作。
若要调试多线程应用程序,请参阅 如何:使用“线程”窗口。
实现自己的支持异步编程模型的组件。 有关详细信息,请参阅 基于事件的异步模式概述。
谨慎
在使用任何类型的多线程时,您可能会使自己面临非常严重和复杂的错误。 在实现使用多线程处理的任何解决方案之前,请参阅 托管线程处理最佳做法。