在很多场合下, 你需要在主(UI)线程中运行一些比较耗时间的任务,比如以下的任务
l Image downloads
l Web service invocations
l File downloads and uploads (including for peer-to-peer applications)
l Complex local computations
l Database transactions
l Local disk access, given its slow speed relative to memory access
这个时候UI就会陷入一种假死的状态,会给用户带来一种很不好的体验. 如何在这里发挥多线程的优势以改善用户体验? .Net2.0的System.ComponentModel.BackgroundWorker为我们提供了一个很方便的解决方法.
在vs.net2005 101 sample中提供了一个计算素数的例子, 不过那个例子并没有全面演示BackgroundWorker的能力, 尤其是没有对线程工作过程(ReportProgress)中的能力做比较好的演示.因此我重新做了一个Demo.
这个例子很简单, 就是将左边列表中的内容移至的右边, 用一个进度条来显示移动的进度, 当然既然是BackgroundWorker这个时候主界面可以进行其他操作.
本文的源代码提供下载, 其中有详细注释, 所以我在此简要介绍一下需要注意的地方.
BackgroundWorker主要通过对DoWork ProgressChanged RunWorkerCompleted三个事件的处理来完成任务. 需要注意在DoWork中不能直接操作主界面的元素.比如你在MainForm类中启动了一个BackgroundWorker, 在DoWork的处理方法中不能直接调用任何MainForm中的成员变量. 但是在ProgressChanged 和 RunWorkerCompleted的事件处理中则无此限制, 可以在后台线程中直接调用主线程中的元素, 这是BackgroundWorker中最有亮点的地方. 虽然在DoWork的处理方法中不能调用但是它也提供了参数传递的方法,可以间接调用.示例如下:
27 //If your background operation requires a parameter,
28 //call System.ComponentModel.BackgroundWorker.RunWorkerAsync
29 //with your parameter. Inside the System.ComponentModel.BackgroundWorker.DoWork
30 //event handler, you can extract the parameter from the
31 //System.ComponentModel.DoWorkEventArgs.Argument property.
32 worker.RunWorkerAsync(leftList);
27 private void worker_DoWork(object sender, DoWorkEventArgs e)
28 {
29 MoveList((BackgroundWorker)sender,e);
30 }
31
32 private void MoveList(BackgroundWorker worker,DoWorkEventArgs e)
33 { //get leftList in Main UI Thread from arguments
34 IList<string> list = e.Argument as IList<string>;
35 //...
36 }
而在ProgressChanged和RunWorkerCompleted事件的处理方法中则更加简单.
27 private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
28 {
29 //Add string to the right listBox, we use rightList in Main UI Thread directly
30 rightList.Add(e.UserState as string);
31 }
上述原则可以说是BackgroundWorker最需要注意的地方.
另外一个容易被人粗心漏过的地方是有关属性的设置.
如果你要使BackgroundWorker支持进度汇报和取消功能别忘了在初始化的时候为下面两个属性赋值.
// Specify that the background worker provides progress notifications
worker.WorkerReportsProgress = true;
// Specify that the background worker supports cancellation
worker.WorkerSupportsCancellation = true;
其它部分就让大家自己看代码吧.
BackgroundWorker内部实现是基于delegate的异步调用.