C# 异步调用机制的理解(一)


        通常,在进行一个对象的方法调用时,对象执行期间客户端通常都是堵塞的,只有等方法执行完毕返回控制权时才会回到客户端,然而某些时候,我们需要异步调用方法,即对象在后台执行方法调用,控制权可以立即返回到客户端,随后能以某种方式通知客户端已经执行完毕,这种执行模式就是所谓的异步调用方法,而操作就是大家所知的异步调用。异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。 

.NET Framework 为异步操作提供两种设计模式:
  • 使用 IAsyncResult 对象的异步操作。

  • 使用事件的异步操作

今天主要讲的是使用 IAsyncResult 对象的异步操作,利用一个委托来进行异步编程。

       一: 异步调用编程模型
        支持异步调用使用多线程肯定是必须的,但是如果每一次异步调用都创建一个新线程的话肯定是一个资源的浪费。在.Net中,为我们提供了线程池,线程池在我们每次进行异步调用的时候都会给调用方分配一个线程,然后控制权会在很短的时间内返回到客户端。 总的来说BeginInvoke()方法发起一个异步调用,EndInvoke()管理方法的完成,包括获取输出参数和返回值。要进行异步调用的说明还需要理解很多其他的概念,包括委托,事件,多线程等,这些概念我就不一一道来了。
下面是整个说明过程中用到的一个类(摘抄而来):
       

        public class CalCulator
        {
            public int Add(int argument1, int argument2)
            {
                return argument1 + argument2;
            }

            public int Subtract(int argument1, int argument2)
            {
                return argument1 - argument2;
            }
        }

一个委托: public delegate int BinaryOperation(int argument1, int argument2);

二:使用beginInvoke()和EndInvoke() 方法
        异步委托提供以异步方式调用同步方法的能力。当同步调用一个委托时,“Invoke”方法直接对当前线程调用目标方法。如果编译器支持异步委托,则它将生成“Invoke”方法以及“BeginInvoke”和“EndInvoke”方法。如果调用“BeginInvoke”方法,则公共语言运行库 (CLR) 将对请求进行排队并立即返回到调用方。将对来自线程池的线程调用该目标方法。提交请求的原始线程自由地继续与目标方法并行执行,该目标方法是对线程池线程运行的。如果在对“BeginInvoke”方法的调用中指定了回调方法,则当目标方法返回时将调用该回调方法。在回调方法中,“EndInvoke”方法获取返回值和所有输入/输出参数。如果在调用“BeginInvoke”时未指定任何回调方法,则可以从调用“BeginInvoke”的线程中调用“EndInvoke”。
    对于上面的文字意思用代码表示如下:   
        1.编译器生成的BinaryOperaion 定义:

        public sealed class BinaryOperaion : MulticastDelgate
        {
            public BinaryOperaion(object target, int methodPtr)
            {
                ...
            }

            public virtual int Invoke(int argument1, int argument2)
            { ...}

            public virtual IAsyncResult BeginInvoke(int argument1, int argument2, AsyncCallback callback, object asyncState)
            {
                ...
            }

            public virtual int EndInvoke(IAsyncResult result)
            { ...}
        }

    2.同步调用:        

        public delegate int BinaryOperation(int argument1, int argument2);

        CalCulator calculator = new CalCulator();
        BinaryOperaion oppDel = calculator.Add;
        oppDel(2,3);

    3 .异步调用:
        首先说明下BeginInvoke()方法返回的IAsyncResult接口对象,在.Net中IAsyncResult的定义如下:

           // 摘要:
    //     表示异步操作的状态。
    [ComVisible(true)]
    public interface IAsyncResult
    {
        // 摘要:
        //     获取用户定义的对象,它限定或包含关于异步操作的信息。
        //
        // 返回结果:
        //     用户定义的对象,它限定或包含关于异步操作的信息。
        object AsyncState { get; }
        //
        // 摘要:
        //     获取用于等待异步操作完成的 System.Threading.WaitHandle。
        //
        // 返回结果:
        //     用于等待异步操作完成的 System.Threading.WaitHandle。
        WaitHandle AsyncWaitHandle { get; }
        //
        // 摘要:
        //     获取异步操作是否同步完成的指示。
        //
        // 返回结果:
        //     如果异步操作同步完成,则为 true;否则为 false。
        bool CompletedSynchronously { get; }
        //
        // 摘要:
        //     获取异步操作是否已完成的指示。
        //
        // 返回结果:
        //     如果操作完成则为 true,否则为 false。
        bool IsCompleted { get; }
    }
}


从它的定义我们可以看出很多东西, 如获取异步操作是否同步完成的指示,我们可以把IAsyncResult 对象传递给EndInvoke()
方法,来标识你希望检索的那个特定的异步方法;如下所示:

        CalCulator calculator = new CalCulator();
        BinaryOperaion oppDel = calculator.Add;
        IAsyncResult asynResult = oppDel.BeginInvoke(2, 3, null, null);

        int iResult=oppDel.EndInvoke(asynResult);
        System.Diagnostics.Debug.Assert(iResult==5);

虽然例子很简单,但是要运用到实际的程序编写中的话,还是要多下点苦功夫的,在上面有几个关键点,其中使用EndInvoke()方法我们可以获取所有输出参数和方法的返回值,它堵塞了调用者一直到它等待的对象返回。这里有几个需要注意的地方: 
  首先:EndInvoke()方法在每次调用异步操作时只能执行一次;
  其次:当使用委托进行异步调用时,委托内部列表之允许一个目标方法;
   最后,在将IAsynResult对象传递给EndInvoke()方法时,委托和IAsynResult对象必须要匹配。


三:轮询或等待完成
    有的时候客户端只是想知道某个操作是否完成了,或者想等待一段时间然后再做一些有限的处理,然后再继续等待或处理。这个时候我们可以使用IAsynResult的AsyncWaitHandle属性进行堵塞,一直到方法完成 ,方式如下:asyncResult.AsyncWaitHandle.WaitOne();当然也可以指定超时 ;其实在有的时候我们有很多方法需要在异步环境下进行的时候我们可以这样来等待多个方法的完成,我们可以获取到一个WaitHandle数组,然后调用它的WaitAll()方法等待多个异步方法的完成,这个在管理多个异步方法尤其有优势。 


总结: 由于篇幅有限,还有些没讲的怎么详细,希望在下篇能分开慢慢道来,在进行异步的过程中,其实对于怎样完成回调方法也是很有用的,我们可以在回调方法中进行我们需要的一些处理,如发出通知给客户端(这个过程应该是事件驱动的)。 自己其实也只是在一个自动更新模块中用到了一些,理解肯定还不深,还是那句话,多多指教。

原文地址:https://www.cnblogs.com/hanchan/p/893836.html