专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »博文摘选 » 多线程并行计算:浅谈.NET下的多线程和并行计算(十).NET异步编程模型上 »正文

多线程并行计算:浅谈.NET下的多线程和并行计算(十).NET异步编程模型上

来源: 发布时间:星期六, 2010年1月9日 浏览:0次 评论:0
谈多线程谈到现在我们要明确多线程个好处是可以进行并行运算(充分利用多核处理器对于桌面应用来说就更重要点了没有WEB服务器利用多核只能靠自己)还有个好处就是异步操作就是我们可以让某个长时间操作独立运行不妨碍主线程继续进行些计算然后异步去返回结果(也可以不返回)前者能提高性能是能利用到多核而后者能提高性能是能让CPU不在等待中白白浪费其实异步从广义上来说也可以理解为某种并行运算在的前这么多例子中我们大多采用手工方式来新开线程的前也说过了在大并发环境中随便开始和结束线程代价太大需要利用线程池使用线程池话又觉得少了些控制现在让我们来整理总结下大概会有哪几种常见异步编程应用模式:

1) 新开个A线程执行个任务然后主线程执行另个任务后等待线程返回结果后继续

2) 新开个A线程执行个任务然后主线程不断轮询A线程是否执行完毕如果没有话可以选择等待或是再进行些操作

3) 新开个A线程执行个任务执行完毕的后立即执行个回调思路方法去更新些状态变量主线程和A线程不定有直接交互

4) 新开个A线程执行个任务执行完毕的后啥都不做

(补充异步编程不定是依赖于线程从广义上来说使用队列异步处理数据也可以算是种异步编程模式)

对于这任何我们要使用线程池来编写应用话都是比较麻烦比如如下代码实现了1)这种应用:

AsyncObj { public EventWaitHandle AsyncWaitHandle { get; ; } public object Result { get; ; } public AsyncObj { AsyncWaitHandle = AutoReEvent(false); } }

AsyncObj ao = AsyncObj; ThreadPool.QueueUserWorkItem(state => { AsyncObj obj = state as AsyncObj; Console.WriteLine("asyc operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(2000); Console.WriteLine("asyc operation completed @ " + DateTime.Now.("mm:ss")); obj.Result = 100; obj.AsyncWaitHandle.Set; }, ao); Console.WriteLine(" operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(1000); Console.WriteLine(" operation completed @ " + DateTime.Now.("mm:ss")); ao.AsyncWaitHandle.WaitOne; Console.WriteLine("get syc operation result : " + ao.Result. + " @ " + DateTime.Now.("mm:ss"));

结果如下:

image

对于2)-4)等情况又是另外套了这样我们代码可能会变得乱 7 8糟在.NET中我们委托以及很多IO操作相关类库都支持种叫做异步编程模型APM编程模型不仅仅方便了我们进行多线程应用而且我们如果自己要设计类库话也可以遵从这个模式(基于APM接口实现我们自己类库).NET提供了基于IAsyncResult异步编程模型和基于事件异步编程模型这节我们来看看基于IAsyncResult也就是BeginInvoke和EndInvoke(对于非同用操作来说就是BeginXXX和EndXXX)编程模型各种使用思路方法可以说这么多种使用思路方法可以满足我们大部分要求

首先来定义个异步操作:

AsyncOperation( x, y) { Console.WriteLine("asyc operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(2000); a, b; ThreadPool.GetAvailableThreads(out a, out b); Console.WriteLine(.Format("({0}/{1}) #{2}", a, b, Thread.CurrentThread.ManagedThreadId)); Console.WriteLine("asyc operation completed @ " + DateTime.Now.("mm:ss")); x + y; }

我们需要开两个线程同时计算两个异步操作然后主线程等待两个线程执行完毕后获取结果并且输出它们难以想象代码是多么简单:

var func = Func<, , >(AsyncOperation); var result1 = func.BeginInvoke(100, 200, null, null); var result2 = func.BeginInvoke(300, 400, null, null); Console.WriteLine(" operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(1000); Console.WriteLine(" operation completed @ " + DateTime.Now.("mm:ss")); result = func.EndInvoke(result1) + func.EndInvoke(result2); Console.WriteLine("get syc operation result : " + result + " @ " + DateTime.Now.("mm:ss"));

主线程计算需要1秒两个异步线程都需要2秒整个理论上需要2秒执行完毕看看结果如何:

image

当然在的前我们限制了线程池线程数为2-4:

ThreadPool.SetMinThreads(2, 2); ThreadPool.SetMaxThreads(4, 4);

从结果中可以看出使用委托来异步思路方法基于线程池EndInvoke时候阻塞了主线程得到结果后主线程继续在代码中没看到Thread没看到ThreadPool没看到信号量我们却完成了个异步操作实现了开始说1)场景现在再来看看第 2种使用方式:

var func = Func<, , >(AsyncOperation2); var result1 = func.BeginInvoke("hello ", 2000, null, null); var result2 = func.BeginInvoke("world ", 3000, null, null); Console.WriteLine(" operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(1000); Console.WriteLine(" operation completed @ " + DateTime.Now.("mm:ss")); WaitHandle.WaitAny( WaitHandle { result1.AsyncWaitHandle, result2.AsyncWaitHandle }); r1 = result1.IsCompleted ? func.EndInvoke(result1) : .Empty; r2 = result2.IsCompleted ? func.EndInvoke(result2) : .Empty; (.IsNullOrEmpty(r1)) { Console.WriteLine("get syc operation result : " + r2 + " @ " + DateTime.Now.("mm:ss")); func.EndInvoke(result1); } (.IsNullOrEmpty(r2)) { Console.WriteLine("get syc operation result : " + r1 + " @ " + DateTime.Now.("mm:ss")); func.EndInvoke(result2); }

BeginInvoke返回个IAsyncResult通过其AsyncWaitHandle 属性来获取WaitHandle异步完成时会发出信号量这样我们就可以更灵活些了可以在需要时候去WaitOne(可以设置超时时间)也可以WaitAny或是WaitAll上例我们实现效果是开了2个线程个3秒个2秒只要有任何个完成就获取其结果主线程任务完成的后再去EndInvoke没完成那个来释放资源(比如有两个排序算法它们哪个快取决于数据源我们起执行并且只要有个得到结果就继续)在这里我们工作思路方法AsyncOperation2定义如下:

AsyncOperation2( s, time) { Console.WriteLine("asyc operation started @ " + DateTime.Now.("mm:ss:fff")); Thread.Sleep(time); a, b; ThreadPool.GetAvailableThreads(out a, out b); Console.WriteLine(.Format("({0}/{1}) #{2}", a, b, Thread.CurrentThread.ManagedThreadId)); Console.WriteLine("asyc operation completed @ " + DateTime.Now.("mm:ss:fff")); s.ToUpper; }

这段运行结果如下:

image

可以看到在2秒那个线程结束后主线程就继续了然后再是3秒那个线程结束再来看看第 3种也就是使用轮询方式来等待结果:

var func = Func<, , >(AsyncOperation); var result = func.BeginInvoke(100, 200, null, null); Console.WriteLine(" operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(1000); Console.WriteLine(" operation completed @ " + DateTime.Now.("mm:ss")); while (!result.IsCompleted) { Console.WriteLine(" thread wait again"); Thread.Sleep(500); } r = func.EndInvoke(result); Console.WriteLine("get syc operation result : " + r + " @ " + DateTime.Now.("mm:ss"));

输出结果如下这对应我们开始提到第 2种场景在等待时候我们主线程还可以做些(不依赖于返回结果)计算呢:

image

再来看看第 4种采用回调方式来获取结果线程在结束后自动回调思路方法我们可以在回调思路方法中进行EndInvoke:

var func = Func<, , >(AsyncOperation); var result = func.BeginInvoke(100, 200, CallbackMethod, func); Console.WriteLine(" operation started @ " + DateTime.Now.("mm:ss")); Thread.Sleep(1000); Console.WriteLine(" operation completed @ " + DateTime.Now.("mm:ss")); Console.ReadLine;

BeginInvoke第 3个参数是回调思路方法第 4个参数是传给工作思路方法状态变量这里我们把工作思路方法委托传给它这样我们可以在回调思路方法中获取到这个委托:

void CallbackMethod(IAsyncResult ar) { Console.WriteLine(.Format("CallbackMethod runs _disibledevent=>Thread.CurrentThread.ManagedThreadId)); var caller = (Func<, , >)ar.AsyncState; r = caller.EndInvoke(ar); Console.WriteLine("get syc operation result : " + r + " @ " + DateTime.Now.("mm:ss")); }

输出结果如下:

image

可以看到主线程并没有工作线程而阻塞它没有等待它结果异步思路方法结束后自动回调思路方法(运行于新线程)在回调思路方法中我们把状态变量进行类型转换后得到思路方法委托然后通过这个委托来EndInvoke获得结果这里符合我们第3)种应用这种情况下主线程不定需要和异步思路方法进行直接交互(也就无需等待)当然主线程也完全可以再结合使用轮询或等待信号量等待异步线程完成后从共享变量(需要回调思路方法把结果写入共享变量)来获取结果

至于开始说第4)种应用需要注意我们完全可以直接采用线程池来做如果采用异步编程模型即使不需要得到结果也别忘记EndInvoke来释放资源这是个好习惯.NET中很多涉及到IO和网络操作类库都采用了APM方式对于这些应用如果我们不EndInvoke来释放非托管资源GC恐怕无能为力下节继续讨论基于事件异步编程模式

作者:lovecindywang
本文版权归作者和博客园共有欢迎转载但未经作者同意必须保留此段声明且在文章页面明显位置给出原文连接否则保留追究法律责任权利

0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: