说到委托我想大家基本上都用过的,今天这篇文章就来讲解关于委托的异步奥秘。

在我们正常使用的时候很少会去用异步委托技术来提高代码效率。委托的好处就是能对方法进行面向对象的封装,随意传递。在任何组件客户代码中都能对其进行调用,而不是传递方法对象的引用,这样能大大的降低代码的耦合。事件就是运用委托的优势进行对象的消息传递。。[王清培版权所有,转载请给出署名]

那么什么是异步委托呢?简单点讲就是异步的调用一个方法,但是如果我们直接用工作线程(main入口进行来的)去调用方法的话,肯定是做不到异步的。想要异步调用必须用子线程去完成,让主线程能处理一些关键的事情,比如用户界面响应、按钮事件的处理。不能让用户等待这是原则。

下面我们就来学习关于异步委托的相关技术。

我们先看一段简单的代码1:

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.  
  5. namespace ConsoleApplication1  
  6. {  
  7.     /// <summary>  
  8.     /// 用来计算的委托  
  9.     /// </summary>  
  10.     /// <param name="i">参数一</param>  
  11.     /// <param name="y">参数二</param>  
  12.     /// <returns>计算的结果</returns>  
  13.     public delegate int BinaryOperaton(int i, int y);  
  14.     /// <summary>  
  15.     /// 计算类  
  16.     /// </summary>  
  17.     public class Calculator  
  18.     {  
  19.         /// <summary>  
  20.         /// 两数相加  
  21.         /// </summary>  
  22.         /// <param name="argument1"></param>  
  23.         /// <param name="argument2"></param>  
  24.         /// <returns></returns>  
  25.         public int Add(int argument1, int argument2)  
  26.         {  
  27.             return argument1 + argument2;  
  28.         }  
  29.         /// <summary>  
  30.         /// 两数相减  
  31.         /// </summary>  
  32.         /// <param name="argument1"></param>  
  33.         /// <param name="argument2"></param>  
  34.         /// <returns></returns>  
  35.         public int Subtract(int argument1, int argument2)  
  36.         {  
  37.             return argument1 - argument2;  
  38.         }  
  39.     }  
  40. }  

这是一段普通的代码,包含一个委托定义、一个计算类。等一下我们就用这个定义的委托来讲解委托的异步调用。。[王清培版权所有,转载请给出署名]

在我们使用委托的时候通常是向下面这种用法2:

  1. //同步调用  
  2.             Calculator calcularor = new Calculator();  
  3.             BinaryOperaton operation = calcularor.Add;  
  4.             Console.WriteLine(operation(10, 20));  
  5.             Console.ReadLine(); 

我们将Calculator实例方法Add添加到委托列表中,然后使用同步调用,也就是直接用委托实例调用。如果这个时候Add方法处理的时间很长,那么主工作线程就会阻塞,一直到Add方法结束才返回。

那么怎样使委托异步调用呢,这里就涉及到委托的真正幕后原理了。

其实当我们定义一个委托的时候,编译器将它编译成从“MulticastDelegate”类中继承的对象,“MulticastDelegate”对象也是从Delegate抽象基类中继承而来的。所以我们定于的委托并不是简简单单的方法的一个包装,里面有复杂的实现逻辑,能很好的支持异步调用,当然异步调用是由基类帮我们实现的,它帮我们申请.NET后台线程池中的线程来进行方法的调用,能让工作线程继续处理重要的事情,而不在那里干等。

我们来看看委托的异步调用代码3:

  1. Calculator calcularor = new Calculator();  
  2. BinaryOperaton operation = calcularor.Add;  
  3. IAsyncResult result = operation.BeginInvoke(10, 20, nullnull);//异步委托只能有一个方法的引用  
  4. Console.ReadLine(); 

通过委托的BeginInvoke来进行异步调用,那么这个方法在哪里定义的呢,是系统在背后默默的自动生成的,由于不同的委托签名使用的BeginInvoke也是不同的,所以这里必须是动态编译生成的。[MSDN:公共语言运行库会自动使用适当的签名为该委托定义 BeginInvokeEndInvoke 方法。]

在代码3中出现了IAsyncResult接口,这是异步状态接口,什么意思呢。就是IAsyncResult持有对异步操作过程中的状态的引用。当然在异步委托中的状态其实是AsyncResult对象所掌握的,我们其实获取的是AsyncResult对象的引用。

图1:

通过这张图能看见IAsyncResult为什么能清楚线程在做什么。我们继续。

由于IAsyncResult接口保存着对异步线程的执行状态,所以我们能通过IAsyncResult接口判断线程是否已经执行完毕。

  1. //AsyncResult中的AsyncDelegate  
  2.             Calculator calcularor = new Calculator();  
  3.             BinaryOperaton operation = calcularor.Add;  
  4.             //IAsyncResult接口在委托使用中返回的强类型是AsyncResult委托异步状态对象  
  5.             AsyncResult result = (AsyncResult)operation.BeginInvoke(10, 20, nullnull);  
  6.             if (!result.EndInvokeCalled)  
  7.             {  
  8.                 //通过AsyncResult可以获取到当初调用BeginInvoke的委托实例  
  9.                 int count = (result.AsyncDelegate as BinaryOperaton).EndInvoke(result);  
  10.                 Console.WriteLine(count);  
  11.             }  
  12.             Console.ReadLine(); 

作为客户代码在我们不知情的情况下我们只能通过IAsyncResult接口来进行统一的异步操作,在.NET平台里到处可以看见异步调用的方法,比如IO命名空间中的一些Stream对象都提供了异步读取流的方式。都需要自己去实现IAsyncResult接口来达到在异步的情况下传递的消息。

有了异步调用当然需要再适当的时候获取执行的结果了。获取异步操作结束的结果有几种方式,可以通过循环等待、等待句柄、异步回调都可以。我们下面来看看这几种方式。

循环等待

  1. //异步调用.循环等待  
  2.            Calculator calcularor = new Calculator();  
  3.            BinaryOperaton operation = calcularor.Add;  
  4.            IAsyncResult result = operation.BeginInvoke(10, 20, nullnull);//异步委托只能有一个方法的引用  
  5.            while (!result.IsCompleted)//通过IsCompleted进行判断异步操作是否已经结束,true已经结束,false未结束  
  6.            {  
  7.                int count = operation.EndInvoke(result);  
  8.                Console.WriteLine(count);  
  9.            }  
  10.            Console.ReadLine(); 

等待句柄WaitHandle

  1. //轮询等待完成  
  2.            Calculator calcularor = new Calculator();  
  3.            BinaryOperaton operation = calcularor.Add;  
  4.            IAsyncResult result = (AsyncResult)operation.BeginInvoke(10, 20, nullnull);  
  5.            //阻塞当前线程直到方法结束,等待4秒钟。如果当前实例收到信号返回true,否则false.  
  6.            while (true)  
  7.            {  
  8.                if (result.AsyncWaitHandle.WaitOne(4000))  
  9.                    break;  
  10.            }  
  11.            int count = operation.EndInvoke(result);  
  12.            Console.WriteLine(count);  
  13.            Console.ReadLine();  

我们可以通过获取等待句柄进行资源的判断,等待句柄是Windows系统的非托管封转,表示的是对临界资源的逻辑对象。操作系统持有对它的访问全,通过.NET可以很方便的使用这类逻辑对象对我们想要进行同步使用的资源进行抽象,已达到互斥的目的。

上图代码中通过WaitHandle.WaitOne方法进行4秒钟的等待,如果在指定的时间内未能收到信号那么该方法返回false,则继续等待,只有等到返回true才能说明异步操作结束。

异步回调

  1. //异步回调  
  2.            Calculator calcularor = new Calculator();  
  3.            BinaryOperaton operation = calcularor.Add;  
  4.            operation.BeginInvoke(10, 20, OnCompletion, true);//异步回调可以传入适当的参数  
  5.            Console.ReadLine(); 
  1. static void OnCompletion(IAsyncResult asyresult)  
  2.        {  
  3.            AsyncResult comresult = asyresult as AsyncResult;  
  4.            int count = (comresult.AsyncDelegate as BinaryOperaton).EndInvoke(comresult);  
  5.            if ((bool)comresult.AsyncState == true)  
  6.                Console.WriteLine("true");  
  7.            Console.WriteLine(count);  
  8.  
  9.        } 

异步回调我想是最好用的了,它简单方便而且可以跨越不同的客户代码来进行结束后的处理,通过IAsyncResult接口的AsyncResult实例对象获取到异步实例对象进行操作结果的获取。其实还有一个比较重要的东西就是BeginInvoke方法中的最后一个参数,其实该参数是用来传递回调方法的参数的,由于回调方法的签名是不能变的,只能是用IAsyncResult接口作为参数,所以我们只能通过BeginInvoke方法的最后一个参数进行传递,然后通过AsyncResult对象的AsyncState属性进行获取。

文章最后顺便说一下异步事件的使用。

由于事件是基于委托的所以我们当然可以通过事件进行异步的调用订阅者的方法,但是不能像往常那样直接进行BeginInvoke,只有当委托列表中仅仅有一个委托方法时才能直接BeginInvoke,如果多余一个必须进行循环调用。

  1. //异步事件  
  2.            Delegate[] delegatelist = numberchangle.GetInvocationList();  
  3.            foreach (Delegate dele in delegatelist)//异步事件必须分开调用  
  4.            {  
  5.                (dele as BinaryOperaton).BeginInvoke(10, 20, nullnull);  
  6.            } 

通过Delegate基类我们获取到所以委托列表中的委托实例,然后循环调用各自的方法。。[王清培版权所有,转载请给出署名]