转自:http://hi.baidu.com/smithallen/blog/item/43c7b71abf2ce4f1af51330b.html(百度文库)
通常情况下,如果需要异步执行一个耗时的操作,我们会新起一个线程,然后让这个线程去执行代码。但是对于每一个异步调用都通过创建线程来进行操作显然会对性能产生一定的影响,同时操作也相对繁琐一些。
.Net中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程之后,继续执行下面的代码,这样就实现了代码的并行执行。
使用线程池的好处就是避免了频繁进行异步调用时创建、销毁线程的开销。
当我们在委托对象上调用BeginInvoke()时,便进行了一个异步的方法调用。
当使用异步调用时,更多情况下是为了提升系统的性能。在这种情况下使用异步编程时,需要进行更多的控制,比如当异步执行方法的方法结束时通知客户端、返回异步执行方法的返回值等。
本节就对BeginInvoke()方法、EndInvoke()方法和其相关的IAysncResult做一个简单的介绍。
我们看这样一段代码,它演示了不使用异步调用的通常情况:
主函数:
class Program { static void Main(string[]args) { Console.WriteLine("Client application started! "); Thread.CurrentThread.Name= "Main Thread"; Calculator cal = new Calculator(); intresult = cal.Add(2, 5); Console.WriteLine("Result: {0} ", result); // 做某些其它的事情,模拟需要执行3秒钟 for (int i = 1;i <= 3; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}:Client executed {1} second(s).", Thread.CurrentThread.Name, i); } Console.ReadKey(); } }Calculator类:
public class Calculator { public int Add(int x, int y) { if (Thread.CurrentThread.IsThreadPoolThread) { Thread.CurrentThread.Name = "Pool Thread"; } Console.WriteLine("Method invoked!"); // 执行某些事情,模拟需要执行2秒钟 for (int i = 1;i <= 2; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}:Add executed {1} second(s).", Thread.CurrentThread.Name, i); } Console.WriteLine("Method complete!"); returnx + y; } }
通过这几个方法和属性,有助于我们更好地调试异步调用方法。上面代码中除了加入了一些对线程的操作以外再没有什么特别之处。
我们建了一个Calculator类,它只有一个Add方法,我们模拟了这个方法需要执行2秒钟时间,并且每隔一秒进行一次输出。而在客户端程序中,我们使用result变量保存了方法的返回值并进行了打印。随后,我们再次模拟了客户端程序接下来的操作需要执行2秒钟时间。
运行这段程序,会产生下面的输出:
Client application started!
Method invoked!
Main Thread: Add executed 1 second(s).
Main Thread: Add executed 2 second(s).
Method complete!
Result: 7
Main Thread: Client executed 1 second(s).
Main Thread: Client executed 2 second(s).
Main Thread: Client executed 3 second(s).
Press any key to exit...
如果你确实执行了这段代码,会看到这些输出并不是一瞬间输出的,而是执行了大概5秒钟的时间,因为线程是串行执行的,所以在执行完Add()方法之后才会继续客户端剩下的代码。
采用异步方法实现:
先定义一个AddDelegate委托,并使用BeginInvoke()方法来异步地调用它。
主要函数和参数介绍:
(1) BeginInvoke()方法 除了最后两个参数为AsyncCallback类型和Object类型以外,前面的参数类型和个数与委托定义相同;另外BeginInvoke()方法返回了一个实现了IAsyncResult接口的对象(实际上就是一个AsyncResult类型实例,注意这里IAsyncResult和AysncResult是不同的,它们均包含在.Net Framework中)。
(2) AsyncResult的用途有这么几个:传递参数,它包含了对调用了BeginInvoke()的委托的引用;它还包含了BeginInvoke()的最后一个Object类型的参数;它可以鉴别出是哪个方法的哪一次调用,因为通过同一个委托变量可以对同一个方法调用多次。
(3) EndInvoke()方法 接受IAsyncResult类型的对象(以及ref和out类型参数,这里不讨论了,对它们的处理和返回值类似),所以在调用BeginInvoke()之后,我们需要保留IAsyncResult,以便在调用EndInvoke()时进行传递。
这里最重要的就是EndInvoke()方法的返回值,它就是方法的返回值。
注意:当客户端调用EndInvoke()时,如果异步调用的方法没有执行完毕,则会中断当前线程而去等待该方法,只有当异步方法执行完毕后才会继续执行后面的代码。所以在调用完BeginInvoke()后立即执行EndInvoke()是没有任何意义的。我们通常在尽可能早的时候调用BeginInvoke(),然后在需要方法的返回值的时候再去调用EndInvoke(),或者是根据情况在晚些时候调用。说了这么多,我们现在看一下使用异步调用改写后上面的代码吧:
//定义委托 public delegate intAddDelegate(int x, inty); class Program { static void Main(string[]args) { Console.WriteLine("Client application started! "); Thread.CurrentThread.Name= "Main Thread"; Calculatorcal = new Calculator(); //定义委托变量 AddDelegate del = new AddDelegate(cal.Add); //委托的异步调用 IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null); //做某些其它的事情,模拟需要执行3秒钟 for (int i = 1;i <= 3; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}:Client executed {1} second(s).", Thread.CurrentThread.Name, i); } //调用异步委托的结果 intrtn = del.EndInvoke(asyncResult); Console.WriteLine("Result: {0} ", rtn); Console.ReadKey(); } }此时的输出为:
Client application started!
Method invoked!
Main Thread: Client executed 1 second(s).
Pool Thread: Add executed 1 second(s).
Main Thread: Client executed 2 second(s).
Pool Thread: Add executed 2 second(s).
Method complete!
Main Thread: Client executed 3 second(s).
Result: 7
现在执行完这段代码只需要3秒钟时间,两个for循环所产生的输出交替进行,这也说明了这两段代码并行执行的情况。
可以看到Add()方法是由线程池中的线程在执行,因为Thread.CurrentThread.IsThreadPoolThread返回了True,同时我们对该线程命名为了Pool Thread。另外我们可以看到通过EndInvoke()方法得到了返回值。
获取异步委托的执行结果:
有时候,我们可能会将获得返回值的操作放到另一段代码或者客户端去执行,而不是向上面那样直接写在BeginInvoke()的后面。比如说我们在Program中新建一个方法GetReturn(),此时可以通过AsyncResult的AsyncDelegate获得del委托对象,然后再在其上调用EndInvoke()方法,这也说明了AsyncResult可以唯一的获取到与它相关的调用了的方法(或者也可以理解成委托对象)。所以上面获取返回值的代码也可以改写成这样:
static int GetReturn(IAsyncResult asyncResult) { //转换为AsynResult对象 AsyncResult result = (AsyncResult)asyncResult; //获取AsynResult对象中保存的委托 AddDelegate del =(AddDelegate)result.AsyncDelegate; //获取委托结果 int rtn =del.EndInvoke(asyncResult); return rtn; }然后再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn = GetReturn(asyncResult);。
注意:上面IAsyncResult要转换为实际的类型AsyncResult才能访问AsyncDelegate属性,因为它没有包含在IAsyncResult接口的定义中。
补充说明:
BeginInvoke()的另外两个参数分别是AsyncCallback和Object类型
其中:
(1) AsyncCallback是一个委托类型,它用于方法的回调,即是说当异步方法执行完毕时自动进行调用的方法。它的定义为:
public delegate void AsyncCallback(IAsyncResult ar);
即,返回结果类型为void,参数类型为IAsyncResult
(2) Object类型用于传递任何你想要的数值,它可以通过IAsyncResult的AsyncState属性获得。
下面我们将获取方法返回值、打印返回值的操作放到了OnAddComplete()回调方法中:
//定义委托 public delegate intAddDelegate(int x, inty); class Program { static void Main(string[]args) { Console.WriteLine("Client application started! "); Thread.CurrentThread.Name= "Main Thread"; Calculatorcal = new Calculator(); //定义委托对象 AddDelegatedel = new AddDelegate(cal.Add); string data = "Any data you want to pass."; //定义委托异步执行之后的回调函数 AsyncCallback callBack = new AsyncCallback(OnAddComplete); // 委托的异步调用(不需要保存返回结果) del.BeginInvoke(2, 5, callBack, data); //做某些其它的事情,模拟需要执行3秒钟 for (int i = 1;i <= 3; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}:Client executed {1} second(s).", Thread.CurrentThread.Name, i); } Console.ReadKey(); } //具体的回调函数(获取,并输出执行结果) static void OnAddComplete(IAsyncResult asyncResult) { AsyncResult result = (AsyncResult)asyncResult; AddDelegatedel = (AddDelegate)result.AsyncDelegate; //获取输入参数 string data = (string)asyncResult.AsyncState; //获取异步执行的结果 int rtn = del.EndInvoke(asyncResult); Console.WriteLine("{0}: Result, {1}; Data: {2} ", Thread.CurrentThread.Name, rtn, data); } }它产生的输出为:
Client application started!
Method invoked!
Main Thread: Client executed 1 second(s).
Pool Thread: Add executed 1 second(s).
Main Thread: Client executed 2 second(s).
Pool Thread: Add executed 2 second(s).
Method complete!
Pool Thread: Result, 7; Data: Any data youwant to pass.
Main Thread: Client executed 3 second(s).
这里有几个值得注意的地方:
1、我们在调用BeginInvoke()后,不再需要保存IAysncResult了,因为AysncCallback委托将该对象定义在了回调方法的参数列表中;
2、我们在OnAddComplete()方法中获得了调用BeginInvoke()时最后一个参数传递的值,字符串“Any data youwant to pass”;
3、执行回调方法的线程并非客户端线程Main Thread,而是来自线程池中的线程Pool Thread。
另外,如前面所说,在调用EndInvoke()时有可能会抛出异常,所以在应该将它放到try/catch块中,这里我就不再示范了。