C# 异步编程(BeginInvoke和EndInvoke)
BeginInvoke和EndInvoke
在学习这些异步编程模式的示例之前,先研究一下Begin1nvoke和End1nvoke方法。有关
Beginlnvoke的重要事项如下。
- 在调用BeginInvoke时,参数列表中的实际参数组成如下:
- 引用方法需要的参数;
- 两个额外的参数—callback参数和state参数。
- BeginInvoke从线程池中获取一个线程并且让引用方法在新的线程中开始运行。
- BeginInvoke返回给调用线程一个实现IAsyncResult接口的对象的引用。这个接口引用包
含了在线程池线程中运行的异步方法的当前状态。然后原始线程可以继续执行。
如下的代码给出了一个调用委托的BeginInvoke方法的示例。第一行声明了叫作MyDeI的委
托类型。下一行声明了一个和委托匹配的叫作sum的方法。
- 之后的行声明了一个叫作del的MyDel委托类型的委托对象,并且使用sum方法来初始化
它的调用列表。 - 最后一行代码调用了委托对象的Begin1nvoke方法并且提供了两个委托参数3和5,以及
两个BeginInvoke参数callback和state,它们在本例中都设为nullo执行后,BeginInvoke
方法执行两个操作。- 从线程池中获取一个线程并且在新的线程上开始运行sum方法,将3和5作为实参。
- 它收集新线程的状态信息并且把IAsyncResult接口的引用返回给调用线程来提供这些
信息。调用线程把它保存在一个叫作iar的变量中。
delegate long MyDel(int first, int second); // 委托声明 [1]()static long Sum(int x, int y) { return x + y; } // 与委托匹配的方法 [1]()// 委托对象创建与异步调用
MyDel del = new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3, 5, null, null); // 异步调用委托 [1]()
EndInvoke方法用来获取由异步方法调用返回的值,并且释放线程使用的资源。EndInvoke有
如下的特性。
- 它接受一个由方法返回的工AsyncResult对象的引用作为参数,并找到它关联
的线程。 - 如果线程池的线程已经退出,则EndInvoke做如下的事情。
- 清理退出线程的状态并释放其资源。
- 找到引用方法返回的值并把它作为返回值返回。
- 如果当EndInvoke被调用时线程池的线程仍然在运行,调用线程就会停止并等待它完成,
然后再清理并返回值。因为EndInvoke是为开启的线程进行清理,所以必须确保对每一个
BeginInvoke都调用EndInvoke。 - 如果异步方法触发了异常,则在调用EndInvoke时会抛出异常。
如下的代码行给出了一个调用End1nvoke并从异步方法获取值的示例。我们必须把1Async一
Result对象的引用作为参数。
EndInvoke提供了异步方法调用的所有输出,包括ref和out参数。如果委托的引用方法有ref
或out参数,则它们必须包含在EndInvoke的参数列表中,并且在IAsyncResult对象引用之前,如
下所示:
等待直到完成模式
既然我们已经理解了BeginInvoke和EndInvoke方法,下面就来看看异步编程模式吧。我们
要学习的第一种异步编程模式是等待直到完成模式。在这种模式里,原始线程发起一个异步方法
的调用,做一些其他处理,然后停止并等待,直到开启的线程结束。总结如下:
既然我们己经理解了BeginInvoke和EndInvoke方法,下面就来看看异步编程模式吧。我们
要学习的第一种异步编程模式是等待直到完成模式。在这种模式里,原始线程发起一个异步方法
的调用,做一些其他处理,然后停止并等待,直到开启的线程结束。总结如下:
IAsyncResult iar=del.BeginInvoke(3,5,null,null);
//在发起线程中异步执行方法的同时,
//在调用线程中处理一些其他事情
long result=del.EndInvoke(iar);
如下代码给出了一个使用这种模式的完整示例。代码使用Thread类的Sleep方法将它自己
挂起100毫秒(0.1秒)。100毫秒不是很长。但是,如果改为10秒,就无法忍受了。Thread类在
System.Threading.命名空间中。
using System;
using System.Threading;delegate long MyDel(int first,int second); //声明委托class Program
{static long Sum(int x,int y){Console.WriteLine("Thread.Sleep(100)");return x+y;}static void Main(){MyDel del=new MyDel(Sum);Console.WriteLine("Before BeginInvoke");IAsyncResult iar=del.BeginInvoke(3,5,null,null);Console.WriteLine("After BeginInvoke");Console.WriteLine("Doing stuff");long result=del.EndInvoke(iar);Console.WriteLine(#"After EndInvoke:{result}");}
}
AsyncResuIt类
既然我们已经看到了BeginInvoke和EndInvoke的最简单形式,是时候进一步学习IASyncResult
了。它是使用这两个方法时所必需的。
BeginInvoke返回一个IASyncResult接口的引用(该接口由一个AsyncResult类型的类实
现)。AsyncResult类代表了异步方法的状态。图21一20演示了该类中的一些重要部分。有关该类
的重要事项如下。
- 当我们调用委托对象的Beginlnvoke方法时,系统创建了一个AsyncResult类对象。然而,
它不返回类对象的引用,而是返回对象中包含的lAsyncResult接口的引用。 - AsyncResult对象包含一个叫作AsyncDelegate的属性,它返回一个指向被调用来启动异
步方法的委托的引用。但是,这个属性是类对象的一部分而不是接口的一部分。 - lscompleted属性返回一个布尔值,表示异步方法是否完成。
- AsyncState属性返回对象的一个引用,作为BeginInvoke方法调用时的state参数。它返
回类型的引用。
轮询模式
在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用IAsyncResult
对象的Iscomplete属性来定期检查开启的线程是否完成。如果异步方法已经完成,原始线程就
调用End1nvoke并继续。否则,它做一些其他处理,然后过一会儿再检查。在下面的示例中,“处
理”仅仅是由0数到10000000。
delegate long MyDel(int first,int second);class Program
{static long Sum(int x,int y){Console.WriteLine(" Inside Sum");Thread.Sleep(100);return x+y;}static void Main(){MyDel del=new MyDel(Sum); IAsyncResult iar=del.BeginInvoke(3,5,null,null);Console.WriteLine("After BeginInvoke");while(!iar.IsCompleted){Console.WriteLine("Not Done");for(long i=0;i<10_000_000;i++);Console.WriteLine("Done");long result=del.EndInvoke(iar);Console.WriteLine($"Result:{result}");}}
}
回调模式
在之前的等待直到完成模式以及轮询模式中,初始线程仅在知道开启的线程已经完成之后
才继续它的控制流程。然后,它获取结果并继续。
回调模式的不同之处在于,一旦初始线程发起了异步方法,它就自己管自己了,不再考虑同
步。当异步方法调用结束之后,系统调用一个用户自定义的方法来处理结果,并且调用委托的
EndInvoke方法。这个用户自定义的方法叫作回调方法或回调。
BeginInvoke参数列表中最后的两个额外参数由回调方法使用。
- 第一个参数callback是回调方法的名字。
- 第二个参数state可以是null或要传人回调方法的一个对象的引用。可以通过使用
lAsyncResult参数的AsyncState属性来获取这个对象,参数的类型是object。
回调方法
回调方法的签名和返回类型必须和AsyncCallback委托类型所描述的形式一致。这需要方法
接受一个类型的参数并且返回类型是void,如下所示:
void AsyncCallback(IAsyncResult iar)
有多种方式为BeginInvoke方法提供回调方法。由于BeginInvoke中的callback参数是
AsyncCallback类型的委托,我们可以以委托形式提供它,如下面的第一行代码所示。或者,也
可以只提供回调方法名称,让编译器为我们创建委托,两种形式是完全等价的。
IAsyncResult iar1=del.BeginInvoke(3,5,new AsyncCallback(CallWhenDone),null);IAsyncResult iar2=del.BeginInvoke(3,5,CallWhenDone,null);
BeginInvoke的另一个参数(参数列表中的最后一个)用来向回调方法发送对象。它可以是
任何类型的对象,因为参数类型是object。在回调方法中,必须将其转换成正确的类型。
在回调方法内调用Endlnvoke
在回调方法内,我们的代码应该调用委托的EndInvoke方法来处理异步方法执行后的输出值。
要调用委托的EndInvoke方法,肯定需要委托对象的引用,而它在初始线程中,不在开启的线程中。
如果不将BeginInvoke的state参数用于其他目的,可以使用它给回调方法发送委托的引用,
如下所示:
IAsyncResult iar=del.BeginInvoke(3,5,CallWhenDown,del);
否则,可以从作为参数发送给方法的IAsyncResult对象中提取出委托的引用,如下面的代
码及图21-21所示
- 给回调方法的参数只有一个,就是刚结束的异步方法的IAsyncResult接口的引用。请记
住,IAsyncResult接口对象在类对象内部。 - 尽管IAsyncResult接口没有委托对象的引用,封装它的AsyncResult类对象却有委托对象
的引用。所以,示例代码方法体的第一行就通过转换接口引用为类类型来获取类对象的
引用。变量ar现在就有类对象的引用 - 有了类对象的引用,就可以使用类对象的AsyncDelegate属性并且把它转化为合适的委托
类型。这样就得到了委托引用,我们可以用它来调用EndInvoke。
using System.Runtime.Remoting.Messageing;void CallWhenDone(IAsyncResult iar)
{AsyncResult ar=(AsyncResult) iar;MyDel del=(MyDel)ar.AsyncDelegate;long Sum=del.EndInvoke(iar);...
}
如下代码把所有知识点汇总到了一起,给出了一个使用回调模式的示例。
using System;
using Systme.Runtime.Remoting.Messaging;
using System.Threading;delegate long MyDel(int first,int second);class Program
{static long Sum(int x,int y){Console.WriteLine("Inside Sum");Thread.Sleep(100);return x+y;}static void CallWhenDone(IAsyncResult iar){Console.WriteLine("Inside CallWhenDone");AsyncResult ar=(AsyncResult)iar;MyDel del=(MyDel)ar.AsyncResult;long result=del.EndInvoke(iar);Console.WriteLine(" The result is:{0}.",result);}static void Main(){MyDel del=new MyDel(Sum);Console.WriteLine("Before BeginInvoke");IAsyncResult iar=del.BeginInvoke(3,5,new AsyncCallback(CallWhenDone),null);Console.WriteLine("Doint more work in Main.");Thread.Sleep(500);Console.WriteLine("Done with Main.Exiting.");}
}