摘要:昨天在尝试使用System.ComponentModel.BackgroundWorker时,发现这个类的行为和我预料的大不一样,可以说是惊喜。原来以为这个类只是一个线程的简单包装,用多线程模拟了异步调用而已;但是看下面的这段代码:
Thread.CurrentThread.Name = "Main Thread";backgroundWorker1.RunWorkerAsync();
...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){ int i = 0; while (i++ < 100) { backgroundWorker1.ReportProgress(i); Thread.Sleep(50); }}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e){ this.Text = e.ProgressPercentage + "% - " + Thread.CurrentThread.Name;}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){ this.Text = "DONE - " + Thread.CurrentThread.Name;}
毫无疑问,_DoWork方法是运行在另一个不同线程之上的(很容易验证这一点,这也符合BackgroundWorker的设计),而这个方法又调用了backgroundWorker1.ReportProgress方法,触发了ProgressChanged事件。在通常的异步实现,_ProgressChanged方法应该运行于事件触发者相同的线程中;但在这里,它运行于主线程(名为Main Thread的线程)。_RunWorkerCompleted方法也是一样。
在我看来,这个行为非常特别,实际上它也非常有用。这样_ProgressChanged方法体中操作UI控件的代码都无需使用Control.Invoke包装了,让程序的编写大为简化。而我真正感兴趣的是这个类究竟是怎么实现的,我用Reflector打开它的源码之后,原来关键在于它用到了一个名为AsyncOperation的类(System.ComponentModel.AsyncOperation)。
AsyncOperation类有个Post方法,可以用来把一个委托(作为方法指针/列表)提交给另一个线程执行。继续反编译下去,又查到了System.Threading.SynchronizationContext类。不过具体怎么实现是无从得知了,因为追踪到最后,停在了一个[MethodImpl(MethodImplOptions.InternalCall)]的方法,它由CLR本身实现。(我个人猜测,其中很可能利用了Windows API:Get/SetThreadContext,和结构体CONTEXT,改掉了线程上下文。)
退一步说,它怎么实现的并不是那么重要,重要的是我们可以用这个AsyncOperation类实现自己的BackgroundWorker。这里是我写的和上面代码基本等价的实现:
AsyncOperation asyncOperation;SendOrPostCallback progressReporter;Thread workerThread;
public MainForm(){ InitializeComponent();
asyncOperation = AsyncOperationManager.CreateOperation(null); progressReporter = new SendOrPostCallback(ReportProgress); workerThread = new Thread(new ThreadStart(WorkOnWorkerThread));}
private void MainForm_Load(object sender, EventArgs e){ Thread.CurrentThread.Name = "Main Thread"; workerThread.Name = "Worker Thread"; workerThread.IsBackground = true; workerThread.Start();}
void ReportProgress(object obj){ this.Text = obj.ToString() + "% - " + Thread.CurrentThread.Name;}
void WorkOnWorkerThread(){ int i = 0; while......[
阅读全文]