前线解释多线程之委托《三》
前面的两篇关于多线程的文章大体说了下线程的基础应用就没下文了,一是由于最近专业课作业把人搞得乱七八糟,呵呵,今天继续补充,马上要考试了,或许也是本学期最后一篇,之前一直是直接谈多线程,但是等我做上一篇博文小翻译程序的时候,发现如果处理某些逻辑简单的同/异步程序的时候委托是非常方便,有了前两篇博文的基础,这里就不再解释同步和异步了,但是我会在示例程序中反映出来。
同学在处理跨线程的时候用的Invoke()方法处理给我演示,给我说这就是多线程效果,其实Invoke()方法只不过是用来绑定委托调用对象参数的,这种也就只是实现了利用委托,达到同步也就是线程阻塞的效果而已,但是需要注意的是使用的线程都是同一个,所以谈不上真正的多线程。
委托之同步调用
1 using System; 2 using System.Threading; 3 4 namespace 委托同步 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine("故事场景:老板正在陪二奶吃饭,经理去汇报工作"); 11 //输出正在执行的线程的ID 12 Console.WriteLine("当前部门经理汇报工作的酒店客房Id为:{0}",Thread.CurrentThread.ManagedThreadId); 13 Console.WriteLine("在同步模式下委托秘书去Report"); 14 Secretary secretary = new Secretary(Report); 15 string reportContent =secretary.Invoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶"); 16 //这里注意:当秘书完成Report()后下面的内容才会执行 17 Console.WriteLine("终于等到秘书汇报玩工作"); 18 Console.WriteLine("汇报的全部内容为:\n{0}", reportContent); 19 Console.ReadKey(); 20 } 21 static string Report(string formulae, string content) 22 { 23 Console.WriteLine("当前秘书代为汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 24 Console.WriteLine("模拟:老板正在谈给二奶转正的事情,此时间段内秘书也给等着,等了五个小时%^$$#&*"); 25 Thread.Sleep(5000); 26 return formulae+":"+content; 27 } 28 } 29 public delegate string Secretary(string formulae, string content); 30 }
我们先来看看执行效果再说,当然(其中耗时部分的)动态的效果需要你自己复制后执行以下才能看到。
好了,玩笑归玩笑,我们还是看程序的主体,而不要看汉字,嘿嘿,认真看汉字的一定不单纯。
总结一下:
我们从Thread.CurrentThread.ManagedThreadId获取到的线程Id看到,前后执行的任务都是在同一个线程中进行的,而且当我们执行string reportContent =secretary.Invoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶");的时候,发现在这个耗时操作中,后续的程序将停顿执行,知道该任务完成才继续执行后续代码。这样就达到了同步(阻塞)的效果。同时对前面【Invoke()方法只不过是用来绑定委托调用对象参数的】做个简单演示
1 string reportContent =secretary.Invoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶"); 2 //======等效于===============// 3 string reportContent = secretary("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶");
这时候我们觉悟了,他也就只不过是用同一个线程去干几个不同的任务。但是不能一心二用,干一件事,就得放下另一件。
所以以后要是看见别人用Invoke()并且坚持说是执行他所谓的异步(或者两个线程)你就可以可以认为他在胡说,当然观棋不语真君子,自己明白就行了,这个社会,就这样啦,你懂得。
委托之异步调用
这个时候我们应该想前面Report方法执行的那段时间我们可以不可以同时执行别的方法,答案是肯定的,这里就引出了异步调用的概念,等到我们搞定它了再来对比他和委托同步调用的区别。继续今天的追本溯源:
我们先来看一下委托的原始定义:
当然,既然是处理委托肯定是要和delegate关键字打交道,那么我们来追忆一下上面的程序中的一个关键部位,别乱想。
public delegate string Secretary(string formulae, string content);
那么编译的时候,编译器会做什么处理呢,当然你要是追踪的话可不要追踪到Delegate类的源头,那样只会一头雾水。
现在我们看一下编译后编译器做了哪些处理。
于是,我们知道了编译器在处理delegate关键字的时候动态的生成了BeginInvoke()和EndInvoke()方法,我们继续查看一下他们的原型:
1 [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 2 public virtual IAsyncResult BeginInvoke(string formulae, string content, AsyncCallback callback, object @object); 3 [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 4 public virtual string EndInvoke(IAsyncResult result);
我们看到BeginInvoke()方法返回的一个IAsyncResult类型对象,我们转到定义看下提示,IAsyncResult是干什么的
提示是:表示异步操作的状态。MSDN说的很明白,就是最终返回实现的是AsyncResult类,同时查看定义也知道了
EndInvoke()方式并不是必须的,只是用了接受BeginInvoke()方法的返回值,当没有返回值的时候就没必要使用。
说了这么多废话,最重要的地方就是定义中提到的"异步两个字”,所以我们知道可以利用BeginInvoke()实现异步操作。
对于BeginInvoke()后面两个参数默认都是null,他们是用来干什么的呢?继续...
先修改下前面的程序,看看差别:
1 using System; 2 using System.Threading; 3 4 namespace 委托同步 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine("故事场景:老板正在陪二奶吃饭,经理去汇报工作"); 11 //输出正在执行的线程的ID 12 Console.WriteLine("当前部门经理汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 13 Console.WriteLine("在同步模式下委托秘书去Report"); 14 Secretary secretary = new Secretary(Report); 15 IAsyncResult AsyncResult = secretary.BeginInvoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶",null,null); 16 Console.WriteLine("小蜜去汇报吧,我去洗我的桑拿浴!"); 17 //这里注意:当秘书完成Report()后下面的内容才会执行 18 Console.WriteLine("终于等到秘书汇报玩工作"); 19 string reportConten=secretary.EndInvoke(AsyncResult); 20 Console.WriteLine("汇报的全部内容为:\n{0}", reportConten); 21 Console.ReadKey(); 22 } 23 static string Report(string formulae, string content) 24 { 25 Console.WriteLine("当前秘书代为汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 26 Console.WriteLine("模拟:老板正在谈给二奶转正的事情,此时间段内秘书也给等着,等了五个小时%^$$#&*"); 27 Thread.Sleep(5000); 28 return formulae + ":" + content; 29 } 30 } 31 public delegate string Secretary(string formulae, string content); 32 }
运行效果如下:
咋一看貌似,差不多,但是有两个明显不同的特征就是:
第一:前后线程Id不一样,一个是10,一个是9
第二: Console.WriteLine("小蜜去汇报吧,我去洗我的桑拿浴!");该语句不是等到
IAsyncResult AsyncResult = secretary.BeginInvoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶",null,null); 这句执行完了才执行的。
那么它的执行逻辑是:部门经理告诉老板的小蜜你替我汇报,我先去洗桑拿了,待会回来接受你返回的信息(返回主线程,用Console.WriteLine()方法打印出返回消息。)
总结:
上面的确实现了异步,但是你有木有发现,这锅?
string reportConten=secretary.EndInvoke(AsyncResult);
Console.WriteLine("汇报的全部内容为:\n{0}", reportConten);
也就是说,并不是让小蜜去汇报,就和部门经理这条线没关系了,当我们创建了次线程去执行汇报,主线程任然实时监控
小蜜返回的信息。加入这段时间内部门经理洗桑拿回来了,但是小蜜还在汇报,于是急的不时打打电话问问小蜜情况咋样啦
前面提到了,IAsyncResult:表示异步操作的状态。于是它有必要提供了一个状态检测方法,结果看定义果然有滴
(IsCompleted);
于是继续这样修改下代码:
1 using System; 2 using System.Threading; 3 4 namespace 委托同步 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Console.WriteLine("故事场景:老板正在陪二奶吃饭,经理去汇报工作"); 11 //输出正在执行的线程的ID 12 Console.WriteLine("当前部门经理汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 13 Console.WriteLine("在同步模式下委托秘书去Report"); 14 Secretary secretary = new Secretary(Report); 15 IAsyncResult AsyncResult = secretary.BeginInvoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶",null,null); 16 while (!AsyncResult.IsCompleted) 17 { 18 Console.WriteLine("还没汇报完,继续洗我的桑拿浴!"); 19 } 20 //这里注意:当秘书完成Report()后下面的内容才会执行 21 Console.WriteLine("终于等到秘书汇报玩工作"); 22 string reportConten=secretary.EndInvoke(AsyncResult); 23 Console.WriteLine("汇报的全部内容为:\n{0}", reportConten); 24 Console.ReadKey(); 25 } 26 static string Report(string formulae, string content) 27 { 28 Console.WriteLine("当前秘书代为汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 29 Console.WriteLine("模拟:老板正在谈给二奶转正的事情,此时间段内秘书也给等着,等了五个小时%^$$#&*"); 30 Thread.Sleep(5000); 31 return formulae + ":" + content; 32 } 33 } 34 public delegate string Secretary(string formulae, string content); 35 }
我们再截取部分效果图看看运行效果:
结果,不是部门经理疯了就是小蜜疯了吧,一个实时询问,一个适时返回进展情况,尼玛是洗澡还是小蜜是复读机呀。
于是部门经理考虑到小蜜会烦,于是间隔一个小时问一次:代码如下修改局部:
while (!AsyncResult.IsCompleted) { Console.WriteLine("还没汇报完,继续洗我的桑拿浴!"); Thread.Sleep(1000); }
运行效果如下:
于是,洗澡,正事两不误,而且小蜜也觉得经理这人实在。
当然当你觉得这中方式,最好的时候,其实IAsyncResult有提供了一个更加灵活的熟悉AsyncWaitHandle,等到了
获取到用于等待异步操作完成的 WaitHandle的时候,使用WaitOne方法可以更加的灵活的实现这个轮询机制,这个可以自己看下定义,这里就不再多说了,他的作用简答的说就是设置有效时间,超时就返回false。直接看修改的代码:
while (!AsyncResult.AsyncWaitHandle.WaitOne(1000,true)) { Console.WriteLine("还没汇报完,继续洗我的桑拿浴!"); }
运行效果和上面一样:
或许你体验不到他的灵活我们试试,他返回false的情况:
while (!AsyncResult.AsyncWaitHandle.WaitOne(6000,true)) { Console.WriteLine("还没汇报完,继续洗我的桑拿浴!"); } Console.WriteLine("你洗澡好长啊,人家老板和小蜜都走了,你也回家吃饭去吧。");
轻轻一修改,比上面就明显灵活了
上面的几种方法,其实我们都在使用同样的方式进行线程的监控-----那就是"轮询机制",这样是不是感觉很累,于是,beginInvoke()的第三个参数开始出马了----AsyncCallback,一猜就知道是回调函数或者委托一类的东东,我们
来看看官方定义:引用在相应异步操作完成时调用的方法。这就是说,他是在次线程完成以后主动同时调用线程的一种方法
于是,自从有了AsyncCallback经理再也不发愁了,澡也洗好了,而且威望也提升了(引用某广告语,哈哈,学广告的把广告词忘了),我们这样修改下代码:
1 using System; 2 using System.Threading; 3 using System.Runtime.Remoting.Messaging; 4 5 namespace 委托同步 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Console.WriteLine("故事场景:老板正在陪二奶吃饭,经理去汇报工作"); 12 //输出正在执行的线程的ID 13 Console.WriteLine("当前部门经理汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 14 Console.WriteLine("在同步模式下委托秘书去Report"); 15 Secretary secretary = new Secretary(Report); 16 IAsyncResult AsyncResult = secretary.BeginInvoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶", new AsyncCallback(syncCallback), null); 17 Console.WriteLine("经理去干自己的事"); 18 Console.ReadKey(); 19 } 20 static string Report(string formulae, string content) 21 { 22 Console.WriteLine("模拟:老板正在谈给二奶转正的事情,此时间段内秘书也给等着,等了五个小时%^$$#&*"); 23 Thread.Sleep(5000); 24 return formulae + ":" + content; 25 } 26 static void syncCallback(IAsyncResult ar) 27 { 28 Console.WriteLine("当前秘书代为汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 29 Console.WriteLine("汇报工作已经完成"); 30 AsyncResult asyncResult = (AsyncResult)ar; 31 Secretary secretary = (Secretary)asyncResult.AsyncDelegate; 32 Console.WriteLine("汇报的全部内容为:\n{0}", secretary.EndInvoke(ar)); 33 } 34 } 35 public delegate string Secretary(string formulae, string content); 36 }
运行效果如下:
总结:
这样做有什么好处呢,第一,不用因为轮询而造成线程局部资源的浪费,经理不用自己去问,完成报告,移动短信就发给他了。有些人或许疑问,那么第四个参数是干什么的,这个其实就是添加附带一些自定义的额外意义上有用的信息(Object类型的),随便举个小例子。
1 using System; 2 using System.Threading; 3 using System.Runtime.Remoting.Messaging; 4 5 namespace 委托同步 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Console.WriteLine("故事场景:老板正在陪二奶吃饭,经理去汇报工作"); 12 //输出正在执行的线程的ID 13 Console.WriteLine("当前部门经理汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 14 Console.WriteLine("在同步模式下委托秘书去Report"); 15 Secretary secretary = new Secretary(Report); 16 IAsyncResult AsyncResult = secretary.BeginInvoke("亲爱的Boss", "我们公司今年业绩不错,XX也有了二奶", new AsyncCallback(syncCallback), "2012.06.26,本故事由 http://www.cnblogs.com/rohelm 胡编乱造"); 17 Console.WriteLine("经理去干自己的事"); 18 Console.ReadKey(); 19 } 20 static string Report(string formulae, string content) 21 { 22 Console.WriteLine("模拟:老板正在谈给二奶转正的事情,此时间段内秘书也给等着,等了五个小时%^$$#&*"); 23 Thread.Sleep(5000); 24 return formulae + ":" + content; 25 } 26 static void syncCallback(IAsyncResult ar) 27 { 28 Console.WriteLine("当前秘书代为汇报工作的酒店客房Id为:{0}", Thread.CurrentThread.ManagedThreadId); 29 Console.WriteLine("汇报工作已经完成"); 30 AsyncResult asyncResult = (AsyncResult)ar; 31 Secretary secretary = (Secretary)asyncResult.AsyncDelegate; 32 string Info=(string)asyncResult.AsyncState; 33 Console.WriteLine("汇报的全部内容为:\n{0}", secretary.EndInvoke(ar)); 34 Console.WriteLine(Info); 35 } 36 } 37 public delegate string Secretary(string formulae, string content); 38 }
运行效果: