zoukankan      html  css  js  c++  java
  • WCF技术剖析之十一:异步操作在WCF中的应用(上篇)

    按照操作执行所需的资源类型,我们可以将操作分为CPU绑定型(CPU Bound)操作和I/O绑定型(I/O Bound)操作。对于前者,操作的执行主要利用CPU进行密集的计算,而对于后者,大部分的操作处理时间花在I/O操作处理,比如访问数据库、文件系统、网络资源等。对于I/O绑定型操作,我们可以充分利用多线程的机制,让多个操作在自己的线程并发执行,从而提高系统性能和响应能力。服务调用就是典型的I/O绑定型操作,所以多线程在服务调用中具有广泛的应用。在本篇文章中,我们专门来讨论多线程或者是异步操作在WCF中的具体应用。

    如果按照异步操作发生的位置,我个人将WCF应用的异步操作分为下面3种变体。

    • 异步信道调用:客户端通过绑定创建的信道向服务端发送消息,从而实现了对服务的调用,不管消息通过信道向服务端发送的方式是同步的(采用请求-回复MEP进行消息交换)还是异步的(采用单向MEP进行消息交换),客户端程序都可以通过代理对象异步地调用信道,从而实现异步服务调用;
    • 单向(One-way)消息交换:客户端的信道通过单向的消息交换模式向服务端发送消息,消息一旦抵达传输层马上返回,从而达到异步服务调用的效果;
    • 异步服务实现:服务端在具体实现服务操作的时候,采用异步调用的方式。

    图1清晰地揭示了以上3种异步场景在整个服务调用中所发生的时机。对于这3种典型的异步操作,它们之间是相互独立的。对于单向消息交换,由于在上面一节中已经进行过详细的介绍,在本节中主要介绍其余两种异步操作的具体使用。本篇文章我们着重探讨第一种形式(异步信道调用)的异步调用,关于异步服务的实现放在下篇中。

    clip_image002

    图1 WCF多线程应用的三种典型场景

    为了方便客户端进行异步的服务调用,最简便的方式就通过SvcUtil.exe这个代码生成工具帮助我们生成机遇异步调用的服务代理类。由于SvcUtil.exe同时也为VS提供了添加服务引用的实现,异步服务代理也可以通过添加服务引用的方式创建。在具体通过服务代理进行异步服务调用的时候,可以采用不同的调用形式,不仅可以采用参数典型的BeginXxx和EndXxx的形式,也可以采用回调(Callback)的形式,还可以采用事件注册的形式。

    一、异步服务代理的创建

    对于任何一个服务操作,不管它是否采用了异步的实现方式,也不管是否采用单向的消息交换模式,我们均可以通过添加服务引用或者直接使用SvcUtil.exe的方式创建异步服务代理,对服务进行异步调用。

    如果通过添加服务引用的方式来创建异步服务代理,只需要在添加服务引用对话框中点击“高级(Advanced)”按钮,便会弹出如下一个“服务引用设置(Service Reference Settings)”对话框,勾选“生成异步操作(Generate asynchronous operations)”复选框即可,如图2所示。

    clip_image004

    图2 添加服务引用时生成异步操作的设置

    通过这种方式生成的代理类与没有选择“生成异步操作”选项一样,都是生成一个继承自ClientBase<TChannel>的类,所不同的是,该类中会多出一些与异步服务调用相关的成员。我们同样以我们的CalculatorService为例(服务契约的定义如下)。

       1: [ServiceContract(Namespace="urn:artech.com")]
       2: public interface ICalculator
       3: {
       4:     [OperationContract]
       5:     double Add(double x, double y);
       6: }

    通过这种方式生成的代理类CalculateClient会多出下面列出的事件和方法成员。

       1: public partial class CalculateClient : ClientBase< ICalculator>, ICalculator
       2: {
       3:     //其他成员
       4:     public event System.EventHandler<AddCompleteEventArgs> AddComplete;
       5:     public IAsyncResult BeginAdd(double x, double y, AsyncCallback callback, object asyncState)
       6:     {
       7:         //省略实现
       8:     }
       9:     
      10:     public double EndAdd(System.IAsyncResult result)
      11:     {
      12:         //省略实现
      13:     }
      14:     
      15:     public void AddAsync(double x, double y)
      16:     {
      17:         //省略实现
      18:     }
      19:  
      20:     public void AddAsync(double x, double y, object userState)
      21:     {
      22:         //省略实现
      23:     }
      24: }

    事件AddComplete将在Add操作执行之后触发,你可以注册该事件,在运算结束之后做一些特殊的工作,比如运算结果的显示。该事件包含一个特殊的EventArgs:AddCompleteEventArgs。该事件参数类型同样是通过添加服务引用自动创建的。AddCompleteEventArgs继承自System.ComponentModel.AsyncCompleteEventArgs。在事件处理器中可以通过该参数得到异步方法执行的结果(Result属性)和异步操作执行过程中抛出的异常(Error属性),以及得到在执行异步操作显式指定的信息(UserState)。AddCompleteEventArgs和AsyncCompleteEventArgs的定义如下。

       1: public partial class AddCompleteEventArgs : AsyncCompleteEventArgs
       2: {
       3:  
       4:     public AddCompleteEventArgs(object[] results,Exception exception, bool cancelled, object userState) :
       5:         base(exception, cancelled, userState)
       6:     {
       7:         //省略实现
       8:     }
       9:  
      10:     public double Result
      11:     {
      12:         get
      13:         {
      14:            //省略实现
      15:         }
      16:     }
      17: }
       1: public class AsyncCompleteEventArgs : EventArgs
       2: {
       3:     public bool Cancelled { get; }
       4:     public Exception Error { get; }
       5:     public object UserState { get; }
       6: }

    二、通过BeginXxx/EndXxx进行异步服务调用

    接下来我将介绍3种不同的执行异步服务调用的方式,为了简单起见,我们以上面提到的CalculatorService为例演示通过异步操作得到运算结果,并将结果输出。首先采用传统的异步编程模式BeginXxx/EndXxx,如下面的代码所示,在调用BeginAdd方法后,可以做一些额外的处理工作,这些工作将会和Add服务操作的调用并发地运行,最终的运算结果通过EndAdd方法得到。

       1: CalculateClient proxy = new CalculateClient();
       2: IAsyncResult asynResult = proxy.BeginAdd(1, 2, null, null);
       3: //其他操作
       4: double result = proxy.EndAdd(asynResult);
       5: proxy.Close();
       6: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result);

    三、通过回调的方式进行异步服务调用

    通过上面的方式进行异步调用有一个不好的地方,就是当EndAdd方法被执行的时候,如果异步执行的方法Add没有执行结束的话,该方法将会阻塞当前线程并等待异步方法的结束,往往不能起到地多线程并发执行应有的作用。我们真正希望的是在异步执行结束后自动回调设定的操作,这样就可以采用回调的方式来实现这样的机制了。

    在下面的代码中,我们通过一个匿名方法的形式定义回调操作,由于在回调操用中输出运算结果时需要使用到参与运算的操作数,我们通过BeginAdd方法的最后一个object类型参数实现向回调操作传递数据,在回调操作中通过IAsyncResult对象的AsyncState获得。

       1: CalculateClient proxy = new CalculateClient();
       2: proxy.BeginAdd(1, 2,
       3:     delegate(IAsyncResult asyncResult)
       4:     {
       5:         double[] operands = asyncResult.AsyncState as double[];
       6:         double result = proxy.EndAdd(asyncResult);
       7:         proxy.Close();
       8:         Console.WriteLine("x + y = {2} when x = {0} and y = {1}", operands[0], operands[1], result);
       9:     }, new double[]{1,2});

    四、通过事件注册的方式进行异步服务调用

    实际上,事件注册和通过回调从表现上看比较类似,当操作结束之后,对于前者通过触发事件的方式执行相应的操作,而对于后者直接执行指定的回调操作。如果采用事件注册的方式,上面的代码就可以改写成下面的形式。通过AddAsync开始异步操作,如果需要向AddComplete事件传递数据,可以使用该方法的第3个参数userState(该参数和BeginAdd的第4个参数asyncState具有相似的作用),设定的值可以通过AddCompleteEventArgs的UserState属性获得,而操作执行的结果则通过AddCompleteEventArgs的Result属性获得。

       1: CalculateClient proxy = new CalculateClient();
       2: proxy.AddComplete += delegate(object sender, AddCompleteEventArgs args)
       3: {
       4:     double[] operands = args.UserState as double[];
       5:     double result = args.Result;
       6:     proxy.Close();
       7:     Console.WriteLine("x + y = {2} when x = {0} and y = {1}", operands[0], operands[1], result);
       8: };
       9: proxy.AddAsync(1, 2,new double[]{1,2}); 
    作者:Artech
    出处:http://artech.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    数据库字段太多,批量快速建立实体类方法(适合大量字段建立实体类)
    SQL service 中的 ”输入SQL命令窗口“ 打开了 “属性界面” 回到 ”输入SQL命令窗口“
    计算机软件编程英语词汇集锦
    编程常用英语词汇
    svn上传和下载项目
    当启动tomcat时出现tomcat setting should be set in tomcat preference page
    Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor
    eclipse中选中一个单词 其他相同的也被选中 怎么设置
    Spring Boot的@SpringBootApplication无法引入的问题
    最全的SpringCloud视频教程
  • 原文地址:https://www.cnblogs.com/artech/p/1519423.html
Copyright © 2011-2022 走看看