如果某项操作需要很长的时间才能完成,并且不希望用户界面 (UI) 停止响应或阻塞,则可以使用?BackgroundWorker?类在另一个线程上执行此操作。
本演练演示如何使用?BackgroundWorker?类“在后台”执行耗时的计算,同时用户界面保持响应。 演练时,将有一个异步计算 Fibonacci 数列的应用程序。 即使计算大型 Fibonacci 数列需要花费大量时间,但主 UI 线程不会被这种延时中断,并且在计算期间窗体仍会响应。
本演练涉及以下任务:
创建基于 Windows 的应用程序
在窗体中创建?BackgroundWorker
添加异步事件处理程序
添加进度报告和取消支持
在 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
。
从“工具箱”将?Label?控件拖到窗体上,然后将其重命名为?resultLabel
。
从“工具箱”将?ProgressBar?控件拖到窗体上。
可以使用“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 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;
}
由于异步操作将会花费很长的时间,因此通常希望向用户报告进度并允许用户取消操作。?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);
}
此时,可以编译并运行 Fibonacci 计算器应用程序。
按 F5 编译并运行应用程序。
在后台运行计算的同时,将会看到?ProgressBar?显示完成计算的进度。 也可以取消挂起的操作。
对于较小数值,计算应非常快,但对于较大数值,将看到明显的延时。 如果输入 30 或更大的值,应看到有几秒钟的延时,这取决于计算机的速度。 对于大于 40 的值,完成计算可能要花费数分钟或数小时。 在计算器计算较大的 Fibonacci 数列时,注意可以自由地移动窗体、最小化、最大化甚至关闭窗体。 这是因为主 UI 线程不会等待计算完成。