在C#中,委托类型是一个类型安全的、面向对象的函数指针。当我们通过delegate关键字定义一个委托类型后,编译器会给委托类型生成三个方法:Invoke、BeginInvoke和EndInvoke。
例如对于下面委托类型,可以通过ILSpy查看编译器生成的三个方法。

在使用委托的应用中,最常见的就是通过Invoke()方法以同步方式执行委托实例。也就是说,调用委托的线程将会一直等待,直到委托调用完成。
下面看一个同步执行委托的例子,在numberAdd委托实例中,通过Sleep(3000)模拟了一个耗时的操作:
代码的输出为下,从结果中可以看出,主线程执行委托实例过程中将会被阻塞,直到委托实例执行完成,主线程才会继续执行。

在很多应用中,一个方法可能要执行很久,例如加载一个很大的文档,或者执行一个耗时的数据库操作。如果我们使用同步的方式执行方法,那么主线程会一直阻塞,直到这个方法执行完成,表现就是应用程序没有相应,影响用户体验。这时,就可以考虑通过委托的异步性进行方法调用。
开始介绍异步执行委托之前,首先看看BeginInvoke()和EndInvoke()方法,结合上面的委托类型NumberAdd来分析一下这两个方法的参数和返回类型。
介绍过BeginInvoke()和EndInvoke()方法后,看一个异步调用方法的例子。
代码的输出为,可以看到其实异步委托使用了一个新的线程来执行numberAdd实例,这样主线程就不会在BeginInvoke后阻塞,可以继续执行;但是当主线程执行到EndInvoke时,由于异步调用还没有完成,主线程将在EndInvoke处阻塞。

其实这个例子中还是有很大的问题,主线程还是会被阻塞。下面进行一点点改进,通过轮询的方式查看异步调用状态。
在IAsyncResult接口中,通过实现这个接口的实例的IsCompleted属性,可以检测异步操作是否已完成的指示,如果操作完成则为True,否则为False

简单看看IAsyncResult 的成员:
所以,代码中就可以利用IsCompleted来获取异步操作的状态:
同样,IAsyncResult类型实例中还有一个AsyncWaitHandle属性,通过这个属性可以实现更加灵活的等待逻辑。
该属性返回一个WaitHandle类型的实例,通过这个实例的WaitOne方法就可以调用线程和异步方法之间的同步:
通过轮询的方式来检测异步调用方法的执行状态也不是一种很好的实现方式,对于异步方法的调用,最好的实现方式就是通过AsyncCallback委托来指定回调函数。这样在异步方法完成后,异步线程将会主动通知调用线程。
下面例子中提供了一个回调函数NumberAddCompleted,当异步方法执行完成后,回调函数就会被调用。
注意:回调函数中需要得到委托实例,然后才可以调用EndInvoke方法来获取异步方法执行的结果,所以例子中使用了“System.Runtime.Remoting.Messaging”命名空间中的AsyncResult类型来获取委托的实例。
本文介绍了C#编译器为委托类型生成的BeginInvoke()和EndInvoke()方法。通过了一下简单的例子演示了如何通过BeginInvoke()和EndInvoke()方法来完成方法的异步调用。
当我们需要执行耗时的操作,又不希望调用线程被阻塞的时候,就可以考虑使用异步委托。通过委托异步执行的例子可以看出,其实异步委托的底层使用了多线程(直接使用线程池中的线程)。所以,使用异步委托的地方,我们也可以通过多线程的方式实现。