.Net多线程总结(二)-BackgroundWorker

来源:互联网 发布:中年男士服装品牌知乎 编辑:程序博客网 时间:2024/05/21 17:26
导读:
  上篇文章介绍了多种线程的创建方式,以及winform在多线程编程时的特殊性,这篇我们来介绍一下异步编程的经典模式和微软对其的实现
  微软推荐的异步操作模型是事件模型,也即用子线程通过事件来通知调用者自己的工作状态,也就是设计模式中的observer模式,也可以看成是上文中线程类的扩展,最后实现后调用效果类似于
  
  MyThread thread=newMyThread()
  
  
  thread.Work+=newThreadWork(Calculate)
  
  
  thread.WorkComplete+=newWorkComplete(DisplayResult)
  
  
  
  
  Calculate(objectsender, EventArgs e)){
  
  
  ....
  
  
  }
  
  
  
  
  DisplayResult(objectsender, EventArgs e)){
  
  
  ...
  
  
  }
  
  
  
  
  
  
  <例一>
  这个话题已经有许多很好的文章,大家参考http://www.cnblogs.com/net66/archive/2005/08/03/206132.html,其作者在文章后附加有示例项目,项目中的线程类实现了事件发送,线程终止,报告任务进度等一系列必要的功能,大家可以自己去查看代码,我就不赘述了,我主要谈微软对这个模式的实现BackgroundWorker
  上篇文章里说到了控制权的问题,上面的模型在winform下使用有个问题就是执行上下文的问题,在回调函数中(比如<例一>中的DisplayResult中),我们不得不使用BeginInvoke,才能调用ui线程创建的控件的属性和方法,
  比如在上面net66的例子里
  
  
  //创建线程对象
  _Task =newnewasynchui();
  
  //挂接进度条修改事件
  _Task.TaskProgressChanged +=newTaskEventHandler( OnTaskProgressChanged1 );
  
  
  //在UI线程,负责更新进度条
  privatevoidOnTaskProgressChanged1( objectsender,TaskEventArgs e )
  
  {
  
  if(InvokeRequired ) //不在UI线程上,异步调用
  {
  
  TaskEventHandler TPChanged1 =newTaskEventHandler( OnTaskProgressChanged1 );
  
  this.BeginInvoke(TPChanged1,newobject[] {sender,e});
  
  Console.WriteLine("InvokeRequired=true");
  
  }
  
  else
  {
  progressBar.Value =e.Progress;
  
  }
  
  }
  
  
  
  
  <例二>
  可以看到,在函数里面用到了
  if(InvokeRequired)
  {...BeginInvoke....}
  else
  {....}
  这个模式来保证方法在多线程和单线程下都可以运行,所以线程逻辑和界面逻辑混合在了一起,以至把以前很简单的只需要一句话的任务:progressBar.Value = e.Progress;搞的很复杂,如果线程类作为公共库来提供,对编写事件的人要求会相对较高,那么有什么更好的办法呢?
  其实在.Net2.0中微软自己实现这个模式,制作了Backgroundworker这个类,他可以解决上面这些问题,我们先来看看他的使用方法
  
  System.ComponentModel.BackgroundWorker bw =newSystem.ComponentModel.BackgroundWorker();
  
  
  //定义需要在子线程中干的事情
  bw.DoWork +=newSystem.ComponentModel.DoWorkEventHandler(bw_DoWork);
  
  
  //定义执行完毕后需要做的事情
  bw.RunWorkerCompleted +=newSystem.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
  
  
  //开始执行
  bw.RunWorkerAsync();
  
  
  
  
  staticvoidbw_RunWorkerCompleted(objectsender, System.ComponentModel.RunWorkerCompletedEventArgs e)
  
  {
  
  MessageBox.Show("Complete"+Thread.CurrentThread.ManagedThreadId.ToString());
  
  }
  
  
  
  
  staticvoidbw_DoWork(objectsender, System.ComponentModel.DoWorkEventArgs e)
  
  {
  
  MessageBox.Show(Thread.CurrentThread.ManagedThreadId);
  
  }
  
  
  
  
  
  
  <例三>
  注意我在两个函数中输出了当前线程的ID,当我们在WindowsForm程序中执行上述代码时,我们惊奇的发现,bw_RunWorkerCompleted这个回调函数居然是运行在UI线程中的,也就是说在这个方法中我们不用再使用Invoke和BeginInvoke调用winform中的控件了, 更让我奇怪的是,如果是在ConsoleApplication中同样运行这段代码,那么bw_RunWorkerCompleted输出的线程id和主线程id就并不相同.
  那么BackgroundWorker到底是怎么实现跨线程封送的呢?
  阅读一下这个类的代码,我们发现他借助了AsyncOperation.Post(SendOrPostCallback d, object arg)
  在winform下使用这个函数,就可以使得由SendOrPostCallback定义被封送会UI线程,聪明的博友可以用这个方法来实现自己的BackgroundWorker.
  继续查看下去,发现关键在于AsyncOperation的syncContext字段,这是一个SynchronizationContext类型的对象,而这个对象的Post方法具体实现了封送,当我继续查看
  SynchronizationContext.Post方法时,里面简单的令人难以执行
  
  publicvirtualvoidPost(SendOrPostCallback d, objectstate)
  
  {
  
  ThreadPool.QueueUserWorkItem(newWaitCallback(d.Invoke), state);
  
  }
  
  
  
  这是怎么回事情呢,线程池本省并不具备线程封送的能力啊联想到在Winform程序和Console程序下程序的行为是不同的,而且SynchronizationContext的Post方法是一个virtual方法,我猜测这个方法可能被继承自他的类重写了查询Msdn,果然发现在这个类有两个子类,其中一个就是WindowsFormsSynchronizationContext,我们来看看这个类的Post方法
  
  publicoverridevoidPost(SendOrPostCallback d, objectstate)
  
  {
  
  if(this.controlToSendTo !=null)
  
  {
  
  this.controlToSendTo.BeginInvoke(d, newobject[] { state });
  
  }
  
  }
  
  
  哈哈,又是熟悉的beginInvoke,原来控制台程序和Winform程序加载的SynchronizationContext是不同的,所以行为才有所不同,通过简单的测试,我们可以看到控制台程序直接使用基类(SynchronizationContext),而winform程序使用这个WindowsFormsSynchronizationContext的Post方法把方法调用封送到控件的线程.
  总结:
  同事这个类还提供了进度改变事件,允许用户终止线程,功能全面,内部使用了线程池,能在一定成都上避免了大量线程的资源耗用问题,并通过SynchronizationContext解决了封送的问题,让我们的回调事件代码逻辑简单清晰,推荐大家使用.

本文转自
http://www.cnblogs.com/yizhu2000/archive/2007/10/19/929930.html

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1931627