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  

作者:jillzhang
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
Tag标签: WCF,异步
posted @ 2008-03-27 22:53 Robin Zhang 阅读(4463) 评论(31)  编辑 收藏 网摘 所属分类: WCF

  回复  引用  查看    
#1楼[楼主]2008-03-27 23:23 | jillzhang      
本来计划一个星期写一篇WCF,上两个星期犯懒就没写,惭愧,今天补上
  回复  引用  查看    
#2楼2008-03-27 23:43 | TerryLi      
呵呵,内容不错,坚持吧,懒惰大家都有,加油啊!
  回复  引用  查看    
#3楼2008-03-28 06:58 | 生鱼片      
支持
  回复  引用  查看    
#4楼[楼主]2008-03-28 07:23 | jillzhang      
@TerryLi
@生鱼片
非常感谢二位的支持

  回复  引用  查看    
#5楼2008-03-28 08:15 | 1-2-3      
支持,一定要坚持写完哦。
  回复  引用  查看    
#6楼2008-03-28 08:18 | Jacky Zhong      
支持!正需要这方面的文章,先收藏有空再看。
  回复  引用  查看    
#7楼2008-03-28 08:58 | airwolf2026      
俺一直迷糊的是Begin***和End***操作的时候,到底有没有创建新线程?看MSDN应该是有的,楼主可否解答下?
  回复  引用  查看    
#8楼[楼主]2008-03-28 09:16 | jillzhang      
@airwolf2026
俺一直迷糊的是Begin***和End***操作的时候,到底有没有创建新线程?看MSDN应该是有的,楼主可否解答下?
-----------------------------------------------------------------
明确的说是有的。
当BeginXXX的时候便会有一个新的逻辑线程,注意是逻辑的,因为这个线程可能来源于线程池中空闲的线程,这个线程负责将请求传达给实际执行任务的网络驱动器或者磁盘驱动器,然后他有三种行为来处理结果
1)阻塞等待
这种方式是
BeginXXX(....,null,null);
...
EndXXX(..)
它会一直阻塞异步线程,直到EndXXX后,该异步线程才会逻辑终止,注意也不是物理消亡,这种方式明显有问题,和同步无异,设置还不如同步呢。不提倡
2.轮询状态
BeginXXX方法方法的是IAsyResult对象,IAsyResult有IsCompleted方法可以获取异步操作是否完成,轮询便基于此
IAryResult ar = BeginXXX(....,null,null);
while(!ar.IsCompleted)
{
//还未完成时的操作
Thread.Sleep(100);//这样防止给CPU大哥累晕
}
EndXXX(..);
该方法和阻塞等待也没什么太大差别,也不推荐使用
3.通知机制
EndXXX(..)之后,执行任务的单元会将程序的控制权交还给BeginXXX()的时候指定的某个委托实例,从而由任务执行单元来通知异步线程执行结果
方式为:
{
BeginXXX(....,OnCompleted,asyState);
...
}
void OnCompleted(IAsyResult ar)
{
EndXXX(..);
}
该方式效率是非常高的,值得推荐的做法




  回复  引用  查看    
#9楼[楼主]2008-03-28 09:17 | jillzhang      
不知上述的回答对你的问题是否有帮助
  回复  引用    
#10楼2008-03-28 09:35 | wit2[未注册用户]
楼主:
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。
异步操作最初源于硬件,所有的程序最终都会由硬件来执行,硬件都有明确的DMA模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以在DOS(单进程且无线程)系统中也同样可以发起异步的DMA操作。
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。


  回复  引用  查看    
#11楼2008-03-28 09:38 | winzheng      
继续学习。。。
  回复  引用    
#12楼2008-03-28 09:40 | wit2[未注册用户]
End..必须调用
方法:1.进行某些操作,然后调用 EndInvoke 一直阻塞到调用完成。2.使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用 EndInvoke。
3.轮询由 BeginInvoke 返回的 IAsyncResult,然后调用 EndInvoke。4.将用于回调方法的委托传递给 BeginInvoke。该方法在异步调用完成后在 ThreadPool 线程上执行,它可以调用 EndInvoke。




  回复  引用  查看    
#13楼[楼主]2008-03-28 09:41 | jillzhang      
@wit2
您说的没错,和我说的也没什么矛盾

  回复  引用    
#14楼2008-03-28 09:43 | 苹果非[未注册用户]
喜欢研究WCF的朋友欢迎加入QQ群:13960774
  回复  引用  查看    
#15楼[楼主]2008-03-28 09:44 | jillzhang      
2.使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用 EndInvoke。
3.轮询由 BeginInvoke 返回的 IAsyncResult,然后调用 EndInvoke
-------------------------------------------------------
这两种都是轮询,实现方式不同,但都是基于轮询的

  回复  引用  查看    
#16楼[楼主]2008-03-28 09:44 | jillzhang      
@苹果非
不用QQ怎么办?

  回复  引用    
#17楼2008-03-28 09:45 | wit2[未注册用户]
--引用--------------------------------------------------
jillzhang: @wit2
您说的没错,和我说的也没什么矛盾
--------------------------------------------------------
看这个 “您” ,我有那么老吗?

  回复  引用  查看    
#18楼[楼主]2008-03-28 09:46 | jillzhang      
@wit2
估计没我老,哈哈

  回复  引用  查看    
#19楼2008-03-28 09:47 | airwolf2026      
@jillzhang
多谢楼主的耐心解答.

另外,俺又顺道去看了老赵之前的文章中"请注意,如果没有特殊说明,本文中出现的“线程”所指的是CLR线程池(Thread Pool)中的托管线程,它和Windows线程或纤程(fiber)并不是同一个的概念。同样,它也不是指System.Thread类的实例。"


以前看的时候,没有注意这句话,看来CLR中的线程和Winddows中的线程是有区别的?(托管的区别除外)


  回复  引用    
#20楼2008-03-28 09:47 | wit2[未注册用户]
--引用--------------------------------------------------
jillzhang: 2.使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用 EndInvoke。
3.轮询由 BeginInvoke 返回的 IAsyncResult,然后调用 EndInvoke
-------------------------------------------------------
这两种都是轮询,实现方式不同,但都是基于轮询的
--------------------------------------------------------

对,但它们实现方式差别很大了哦,这样提出来还是有必要吧 :-)

  回复  引用  查看    
#21楼[楼主]2008-03-28 09:51 | jillzhang      
@airwolf2026
原理上说,CLR线程和Fiber有些类似

  回复  引用  查看    
#22楼[楼主]2008-03-28 09:52 | jillzhang      
@wit2
恩,你不说,我真真忘记那种做法了。多谢多谢

  回复  引用    
#23楼2008-03-28 09:55 | wit2[未注册用户]
--引用--------------------------------------------------
jillzhang: @wit2
恩,你不说,我真真忘记那种做法了。多谢多谢
--------------------------------------------------------
客气,写的不错,给你Digg了

  回复  引用  查看    
#24楼2008-03-28 16:29 | wuhang      
受益匪浅,以前看异步的时候,只知道建立个新线程,后来接触了AsyncBeginXXX的方法,看了Remoting还有实践触发,没想到还有这么多,线程池什么的,受益良多呀!借来用用,装饰装饰自己的Blog,楼主不介意吧??
  回复  引用  查看    
#25楼2008-03-28 17:11 | fox23      
呵呵,不错~
不晓得N多用户并发访问Threadpool能否应付哦~
:-)

  回复  引用  查看    
#26楼[楼主]2008-03-28 17:14 | jillzhang      
@fox23
Threadpool是有上限的,当全部的线程均处于工作的时候,并且线程达到了最大值,那么任务也会被排队

  回复  引用    
#27楼2009-03-31 17:05 | s23434[未注册用户]
多个线程是在同一个进程空间里运行的,一个线程崩了,仅会导致这个线程对应的那个进程崩溃。在windows下,进程才是资源分配的最小单位,线程是共享进程的资源。呵呵,有点吹毛求疵了。



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1126197




历史上的今天:
2006-03-27 一个简单的分页控件

相关文章:

相关链接: