朱燚

--书到读透处,酒于微醺时

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  61 随笔 :: 21 文章 :: 445 评论 :: 37 引用

上篇文章介绍了多种线程的创建方式,以及winform在多线程编程时的特殊性,这篇我们来介绍一下异步编程的经典模式和微软对其的实现


微软推荐的异步操作模型是事件模型,也即用子线程通过事件来通知调用者自己的工作状态,也就是设计模式中的observer模式,也可以看成是上文中线程类的扩展,最后实现后调用效果类似于

MyThread thread=new MyThread()

thread.Work
+=new ThreadWork(Calculate)

thread.WorkComplete
+=new WorkComplete(DisplayResult)



Calculate(
object sender, EventArgs e)){

....

}



DisplayResult(
object sender, EventArgs e)){

...

}



 

<例一>

这个话题已经有许多很好的文章,大家参考http://www.cnblogs.com/net66/archive/2005/08/03/206132.html,其作者在文章后附加有示例项目,项目中的线程类实现了事件发送,线程终止,报告任务进度等一系列必要的功能,大家可以自己去查看代码,我就不赘述了,我主要谈微软对这个模式的实现BackgroundWorker

上篇文章里说到了控制权的问题,上面的模型在winform下使用有个问题就是执行上下文的问题,在回调函数中(比如<例一>中的DisplayResult中),我们不得不使用BeginInvoke,才能调用ui线程创建的控件的属性和方法,

比如在上面net66的例子里

//创建线程对象 _Task = new newasynchui();
//挂接进度条修改事件 _Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged1 );

//在UI线程,负责更新进度条 private void OnTaskProgressChanged1( object sender,TaskEventArgs e )
{
if (InvokeRequired ) //不在UI线程上,异步调用 {
TaskEventHandler TPChanged1
= new TaskEventHandler( OnTaskProgressChanged1 );
this.BeginInvoke(TPChanged1,new object[] {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 = new System.ComponentModel.BackgroundWorker();

//定义需要在子线程中干的事情 bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);

//定义执行完毕后需要做的事情 bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

//开始执行 bw.RunWorkerAsync();



static void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
MessageBox.Show(
"Complete"+Thread.CurrentThread.ManagedThreadId.ToString());
}



static void bw_DoWork(object sender, 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方法时,里面简单的令人难以执行

public virtual void Post(SendOrPostCallback d, object state)
{
ThreadPool.QueueUserWorkItem(
new WaitCallback(d.Invoke), state);
}
这是怎么回事情呢,线程池本省并不具备线程封送的能力啊
联想到在Winform程序和Console程序下程序的行为是不同的,而且SynchronizationContext的Post方法是一个virtual方法,我猜测这个方法可能被继承自他的类重写了
查询Msdn,果然发现在这个类有两个子类,其中一个就是WindowsFormsSynchronizationContext,我们来看看这个类的Post方法
public override void Post(SendOrPostCallback d, object state)
{
if (this.controlToSendTo != null)
{
this.controlToSendTo.BeginInvoke(d, new object[] { state });
}
}

哈哈,又是熟悉的beginInvoke,原来控制台程序和Winform程序加载的SynchronizationContext是不同的,所以行为才有所不同,通过简单的测试,我们可以看到控制台程序直接使用基类(SynchronizationContext),而winform程序使用这个WindowsFormsSynchronizationContext的Post方法把方法调用封送到控件的线程.

 总结:

同事这个类还提供了进度改变事件,允许用户终止线程,功能全面,内部使用了线程池,能在一定成都上避免了大量线程的资源耗用问题,并通过SynchronizationContext解决了封送的问题,让我们的回调事件代码逻辑简单清晰,推荐大家使用.

posted on 2007-10-19 09:48 朱燚:-) 阅读(4533) 评论(11)  编辑 收藏 所属分类: .Net研究

评论

#1楼  2007-10-19 12:41 徐少侠      
BackgroundWorker是个简化工作的好东西
微软是越来越将功能封装起来,不让我们直接使用
以后自己起个线程真要成了核心技术了。
  回复  引用  查看    

#2楼  2007-10-20 09:46 西煞魄工人      
微软就是要让一切变得简单~~~
  回复  引用  查看    

#3楼  2007-10-21 09:26 xuwenzhuo      
微软就是要让一切变得简单~~~
---------------------------------------
也让一切变得垃圾~~~
  回复  引用  查看    

#4楼  2007-10-22 10:16 易虎 [未注册用户]
广州网站建设,最好的网络公司
易虎互联 (www.easyhoo.cn)是在现有网络文化内涵的基础上,以全新的网络理念、先进的网络技术和企业管理模式建构的。本公司以多种网络经营为载体,致力于为广大用户提供:各行业各领域、“先进的 、易懂的 、实用的、 普及的”网络商务平台,是一家拥有多元化营销网络及专业服务网络的高科技公司。 2007年易虎互联深入分析互联网行业,对大量企业网站调研,针对当前企业网站的弊端,提出“营销型”网站概念,掀起广州新一轮的企业建网风暴,开创广州企业网站建设蓝海。 我们的口号:“绝不做没有销售力的网站!” QQ:261888234 239898630


  回复  引用    

#5楼  2007-12-05 11:06 boy5d [未注册用户]
好文章,收藏了!
http://ats.boy5d.com
  回复  引用    

#6楼  2008-01-03 19:44 鹤翔      
推荐看看《.NET CLR VIA》,里边对多线程讲解得很透彻
  回复  引用  查看    

#7楼  2008-01-04 14:06 a feng      
”这是怎么回事情呢,线程池本省并不具备线程封送的能力啊“
线程封送是什么意思?
  回复  引用  查看    

#8楼 [楼主] 2008-01-04 14:11 二毛五      
--引用--------------------------------------------------
a feng: ”这是怎么回事情呢,线程池本省并不具备线程封送的能力啊“
线程封送是什么意思?
--------------------------------------------------------
UI线程启动了子线程,子线程执行操作完成后调用回调函数,这时回调函数会在子线程上执行,在本文中封送是指使得回调函数在UI线程上执行
  回复  引用  查看    

#9楼  2008-02-24 15:21 eeesl [未注册用户]
终于知道BackgroudWorker的来源了。。。感谢楼主
  回复  引用    

#10楼  2008-08-12 22:09 leahb [未注册用户]
BackgroundWork 是封装基于事件的异步模式的一个类。为了在给定应用程序模型的适当线程/上下文调用客户端事件处理程序,所以采用 AsyncOperation 和 AsyncOperationManager。
  回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-10-19 09:59 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: