本文内容
- 创建使用后台操作的窗体
- 使用设计器创建 BackgroundWorker
- 添加异步事件处理程序
- 添加进度报告和取消支持
- Checkpoint
如果某项操作需要很长的时间才能完成,并且不希望用户界面 (UI) 停止响应或阻塞,则可以使用 BackgroundWorker 类在另一个线程上执行此操作。
本演练演示如何使用 BackgroundWorker 类“在后台”执行耗时的计算,同时用户界面保持响应。 演练时,将有一个异步计算 Fibonacci 数列的应用程序。 即使计算大型 Fibonacci 数列需要花费大量时间,但主 UI 线程不会被这种延时中断,并且在计算期间窗体仍会响应。
本演练涉及以下任务:
-
创建基于 Windows 的应用程序
-
在窗体中创建 BackgroundWorker
-
添加异步事件处理程序
-
添加进度报告和取消支持
1、创建使用后台操作的窗体
-
在 Visual Studio 中,创建一个名为
BackgroundWorkerExample 的基于 Windows 的应用程序项目(“文件”>“新建”>“项目”>“Visual C#”或“Visual Basic”>“经典桌面”>“Windows 窗体应用程序”)。 -
在“解决方案资源管理器”中,右键单击“Form1”,然后从快捷菜单中选择“重命名”。 将文件名更改为
FibonacciCalculator 。 询问是否希望重命名对代码元素“”的所有引用时,单击“是”Form1 按钮。 -
从“工具箱”将 NumericUpDown 控件拖到窗体上。 将 Minimum 属性设置为
1 ,将 Maximum 属性设置为91 。 -
向窗体添加两个 Button 控件。
-
重命名第一个 Button 控件
startAsyncButton ,并将 Text 属性设置为Start Async 。 重命名第二个 Button 控件cancelAsyncButton ,并将 Text 属性设置为Cancel Async 。 将它的 Enabled 属性设置为false 。 -
为两个 Button 控件的 Click 事件创建一个事件处理程序。
-
从“工具箱”将 Label 控件拖到窗体上,然后将其重命名为
resultLabel 。 -
从“工具箱”将 ProgressBar 控件拖到窗体上。
2、使用设计器创建 BackgroundWorker
可以使用“Windows 窗体设计器”为异步操作创建 BackgroundWorker。
从“工具箱”的“组件”选项卡中,将 BackgroundWorker 拖到窗体上。
3、添加异步事件处理程序
现在已准备好为 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 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; }
-
在 DoWork 事件处理程序中,添加对
ComputeFibonacci 方法的调用。 从 DoWorkEventArgs 的 Argument 属性中获取ComputeFibonacci 的第一个参数。 稍后将 BackgroundWorker 和 DoWorkEventArgs 参数用于进度报告和取消支持。 将ComputeFibonacci 的返回值分配给 DoWorkEventArgs 的 Result 属性。 此结果将可供 RunWorkerCompleted 事件处理程序使用。备注
DoWork 事件处理程序不直接引用
backgroundWorker1 实例变量,因为这将会使此事件处理程序和某个特定的 BackgroundWorker 实例耦合。 相反,引发此事件的 BackgroundWorker 引用将从sender 参数恢复。 当窗体承载多个 BackgroundWorker 时这非常重要。 在 DoWork 事件处理程序中不操作任何用户界面对象也非常重要。 而应该通过 BackgroundWorker 事件与用户界面进行通信。// 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); }
-
在
startAsyncButton 控件的 Click 事件处理程序中,添加启动异步操作的代码。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); }
-
在 RunWorkerCompleted 事件处理程序中,将计算结果分配给
resultLabel 控件。// 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; }
4、添加进度报告和取消支持
由于异步操作将会花费很长的时间,因此通常希望向用户报告进度并允许用户取消操作。 BackgroundWorker 类提供一个在后台操作进行时允许发送进度消息的事件。 它还提供允许辅助代码检测对 CancelAsync 的调用并中断自身的标记。
实现进度报告
-
在“属性”窗口中,选择
backgroundWorker1 。 将 WorkerReportsProgress 和 WorkerSupportsCancellation 属性设置为true 。 -
在
FibonacciCalculator 窗体中声明两个变量。 这将用于跟踪进度。private int numberToCompute = 0; private int highestPercentageReached = 0;
-
为 ProgressChanged 事件添加事件处理程序。 在 ProgressChanged 事件处理程序中,使用 ProgressChangedEventArgs 参数的 ProgressPercentage 属性更新 ProgressBar。
// This event handler updates the progress bar. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.progressBar1.Value = e.ProgressPercentage; }
实现取消支持
-
在
cancelAsyncButton 控件的 Click 事件处理程序中,添加取消异步操作的代码。private void cancelAsyncButton_Click(System.Object sender, System.EventArgs e) { // Cancel the asynchronous operation. this.backgroundWorker1.CancelAsync(); // Disable the Cancel button. cancelAsyncButton.Enabled = false; }
-
下面的
ComputeFibonacci 方法中的代码片段可报告进程并支持取消。if (worker.CancellationPending) { 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); }
5、Checkpoint
此时,可以编译并运行 Fibonacci 计算器应用程序。
按 F5 编译并运行应用程序。
在后台运行计算的同时,将会看到 ProgressBar 显示完成计算的进度。 也可以取消挂起的操作。
对于较小数值,计算应非常快,但对于较大数值,将看到明显的延时。 如果输入 30 或更大的值,应看到有几秒钟的延时,这取决于计算机的速度。 对于大于 40 的值,完成计算可能要花费数分钟或数小时。 在计算器计算较大的 Fibonacci 数列时,注意可以自由地移动窗体、最小化、最大化甚至关闭窗体。 这是因为主 UI 线程不会等待计算完成。