WCF从理论到实践(11)-异步

本文目的

通过阅读本文,您能了解以下知识

1) 如何在WCF中实现异步
2) 异步操作的优缺点及其应用场合
3) 总结对比各种异步操作的实现方式
4) 代码不骗人,实现一个WCF异步小范例

本文适合的读者

本文因为涉及一些常用的基础知识和开发技巧,需要对多线程等具有一定的认识,所以初学者可能不能立即掌握,本文适合WCF中级用户或有其他分布式技术开发经验的WCF初学者

如何在WCF中实现异步

在ARM(异步编程模型)中,我们经常看到BeingXXX(..),EndXXX(..)这样的函数定义,那和他们对应的同步方法还有XXX(..),比如FileStream对象,它既包括同步方式int Read(byte[] buffer,int offset,int count),还有IAsyState BeginRead(byte[] buffer,int offset,int count,IAsyCallback callback,Object asyState)和int EndRead(IAsyState ar)这样的异步方式,如果我们的WCF服务程序也和FileStream设计一样,那我们一些开发人员要跳楼了.本来一个业务方法的实现现在变为了3个,工作量增加了2倍.为何有这样的说法,因为这样的架构不是一个好架构,作为一个优秀的框架,WCF肯定不会犯如此低级的错误,异步与否本来应该是由客户端来决定的,所以我们的服务端实现无需关心异步与否.下面我们来看一下如何实现异步,WCF中实现异步是一件非常简单的事情,我们用svcutil来生成客户端代理代码的时候,只需添加 /async 便可以生成有异步功能的代理类了.而在IDE中,操作就更加简单,就是在添加ServiceReference的时候,选择高级选项,钩选Generate Asynchronus operations,如图:

生成异步操作的代理类下就会增加BeginXXX和EndXXX方法。比如我们示例项目中服务契约中有

[OperationContract] 
string GetData(int value); 

的操作方法,生成的代理类中对应其的异步方法为:

[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetData", ReplyAction="http://tempuri.org/IService1/GetDataResponse")] 
System.IAsyncResult BeginGetData(
int value, System.AsyncCallback callback, object asyncState); 
string EndGetData(System.IAsyncResult result); 

 
异步操作的优缺点及其应用场合

在前面WCF从理论到实践:事件广播文章中,我曾经提到异步操作能提高系统的吞吐能力,老赵同志也曾针对我的说法写了篇正确使用异步操作来校正滥用异步的错误做法,那异步究竟有何优点值得我们使用?而又有什么缺点需要我们使用的时候小心呢?诚如老赵所说,异步并不一定能提高系统性能,甚至因为线程的创建,消亡,和切换会增加系统开销,但异步除了提高性能,还可以增强系统的健壮性。在过去,windows程序总是单线程的,在这样的系统中,如果出现了异常,系统就会 因此而崩溃,甚至连我们的操作系统也是单线程的,所以每次出现异常,我们的计算机用户都要不厌其烦强制关机,然后重启才能解决问题。加入多线程之后,当一个线程上的任务发生异常的时候,其他线程有能力不受影响,从此防止整个应用程序的崩溃。此外如果用户是在一个UI中操作某项耗时的操作,如果不使用异步,那UI线程就会被阻塞,导致界面无法响应,用户就会很无助,增加了异步,让复杂的任务在另外的线程中完成,就会有比较好的用户体验。而且异步并不是说对性能提高没有作用,CLR线程的创建,销毁,和线程上下文切换的确会有很大的开销,比如每创建一个线程,都必须申请1MB的地址空间用于线程的用户模式,申请12KB左右的德地址空间用于线程的内核模式,而且还要求进程调用每个dll中的一个 固定的函数来通知所有的dll系统创建了一个新的线程,同样在销毁的时候,也要做类似的通知,上面这一切似乎都说明了异步操作对于性能的坏处,但事实并非完全如此,我们知道当前的处理器基本上都是双核,或者支持hyper-thread,一个线程的执行总会占用1个cpu逻辑核,如果我们的计算机是4核,8核,而我们不采用异步,那其实多核就没什么太大优势,因为总是1个核在工作,而另外的核却在休息,效率肯定低下,而此时用多线程,就可以充分使用计算机的处理器资源。同时对于一些有IO限制的操作而言,如读取磁盘文件,网络数据相关操作时,整个过程并不是完全靠运算,而是要通过磁盘驱动器或者网络驱动器来协助完成,比如读取磁盘中的一个文件,当应用程序的读取线程发出读请求的时候,该请求会被磁盘驱动器所排队处理,假如它是个很长的操作,那么该操作会在磁盘驱动器上排队或者执行很长时间,而这段时间读线程就处于阻塞的状态,这样就浪费了线程资源,正确的做法应该是线程将读请求发送到磁盘驱动器后马上返回,继续处理其他任务,而当磁盘驱动器操作完成的时候,由磁盘驱动器来通知或者由一个线程来轮询执行状态。这样就防止线程资源被浪费,从而提高系统性能。总结一下上面的说法,异步有三个优点:

1) 在I/O受限等情况下,异步能提高性能,并且能更加充分利用多核CPU的优点。
2) 异步能增强系统健壮性
3) 异步能改善用户体验

同时也有缺点,如下

1) 滥用异步,会影响性能
2) 增加编程难度

总结对比各种异步操作的实现方式

实现异步,主要包含以下几种方法

1)  使用专用线程,方法为:

System.Threading.ThreadStart ts = new System.Threading.ThreadStart(void(object state) target);

System.Threading.Thread th = new System.Threading.Thread(ts);

ts.Start();

调用Start()方法之前,并没有实质性得创建线程资源,而是Start()后才进行创建,此种方式的好处在于能设置线程是前台线程还是后台线程,并且能控制线程的挂起和消亡

2)  使用线程池中的线程

线程是一种比较宝贵的资源,所以使用的时候就要加倍珍惜,线程池中线程在使用完成之后并不是马上销毁,而是回到池中等待下一次的使用,这样就可以较少线程创建的消耗。使用方法如下:

ThreadPool.QueueUserWorkItem(WaitCallback callback)

需要注意的是此种方法使用的均为后台线程   

3)  使用异步编程模型

这种方法是MS推荐的使用方法,该模型普遍格式为:

BeginXXX(…IAsyCallBack callback,object asyState);

EndXXX(IAsyState ar);

这种模型的好处上面已经有所阐述

4) 使用BackgroundWorker

.Net2.0下提供了BackgroundWorker,使用它可以轻易的完成异步操作,并且它还有一些功能上的加强,比如取消操作、

代码不骗人,实现一个WCF异步小范例

非常简单,并不多说,只发项目文件和运行效果图:

项目文件: /Files/jillzhang/Jillzhang.Wcf.APM.rar

参考资料

1)http://www.cnblogs.com/wayfarer/archive/2007/11/09/954256.html  

posted @ 2008-03-27 22:53 Robin Zhang 阅读(...) 评论(...) 编辑 收藏