using System;
using System.Threading;
public class AsyncDemo
{
//首先告诉大家下面的这个方法将会被异步调用。
public string TestMethod(int callDuration, out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = AppDomain.GetCurrentThreadId();
return "MyCallTime was " + callDuration.ToString();
}
}
// 这个委托必须和它即将异步调用的方法具有相同的签名。
public delegate string AsyncDelegate(int callDuration, out int threadId);
(蓝字部分引自MSDN2003>NET Framework 开发员指南>异步编程概述)
第一方案:使用 EndInvoke 等待异步调用
异步执行方法的最简单方式是以 BeginInvoke 开始,对主线程执行一些操作,然后调用EndInvoke。EndInvoke 直到异步调用完成后才返回。这种技术非常适合文件或网络操作,但是由于它阻塞 EndInvoke,所以不要从用户界面的服务线程中使用它。
public class AsyncMain
{
static void Main(string[] args)
{
int threadId;
//创建示例类的实例。
AsyncDemo ad = new AsyncDemo();
// 创建委托
AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
// 委托在这里开始异步调用。
IAsyncResult ar = dlgt.BeginInvoke(3000,
out threadId, null, null);
//人为的线程阻塞。
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
AppDomain.GetCurrentThreadId());
// 委托开始EndInvoke调用,这个过程会使主线程等待异步调用完成并返回结果。
string ret = dlgt.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
}
第二方案:使用 WaitHandle 等待异步调用
等待 WaitHandle 是一项常用的线程同步技术。您可以使用由 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 属性来获取 WaitHandle。异步调用完成时会发出 WaitHandle 信号,而您可以通过调用它的 WaitOne 等待它。
如果您使用 WaitHandle,则在异步调用完成之后,但在通过调用 EndInvoke 检索结果之前,可以执行其他处理。
public class AsyncMain
{
static void Main(string[] args)
{
int threadId;
AsyncDemo ad = new AsyncDemo();
AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
IAsyncResult ar = dlgt.BeginInvoke(3000,
out threadId, null, null);
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
AppDomain.GetCurrentThreadId());
//主线程在这里等待,直到异步线程执行完。
ar.AsyncWaitHandle.WaitOne();
// 和前一方案的区别在于,你可以在异步调用完成后,获取异步调用返回值之前
//在这里做点任何你想作的事。
//调用EndInvoke获取异步调用的返回结果.
string ret = dlgt.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
}
第三方案:轮询异步调用完成
您可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性来发现异步调用何时完成。从用户界面的服务线程中进行异步调用时可以执行此操作。轮询完成允许用户界面线程继续处理用户输入。
public class AsyncMain
{
static void Main(string[] args)
{
int threadId;
AsyncDemo ad = new AsyncDemo();
AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
IAsyncResult ar = dlgt.BeginInvoke(3000,
out threadId, null, null);
// 这里每隔10毫秒就检测(轮询)一下异步执行的状态,
//直到异步调用完成,IsCompleted的值变为ture为止。
while(ar.IsCompleted == false) {
Thread.Sleep(10);
}
//还记得微软的那个善意的提醒吗?虽然IsCompleted为true了,
//我们还是调用一下EndInvoke,来获取返回值。
string ret = dlgt.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
}
第四方方案:异步调用完成时执行回调方法
如果启动异步调用的线程不需要处理调用结果,则可以在调用完成时执行回调方法。回调方法在 ThreadPool 线程上执行。
要使用回调方法,必须将代表该方法的 AsyncCallback 委托传递给 BeginInvoke。也可以传递包含回调方法将要使用的信息的对象。例如,可以传递启动调用时曾使用的委托,以便回调方法能够调用 EndInvoke。
public class AsyncMain
{
private static int threadId;
static void Main(string[] args)
{
AsyncDemo ad = new AsyncDemo();
AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
//注意第三个参数,这就是我们要用到的回调方法。
//第四个参数更为有趣,它可以是任何Object对象,这里它就是
// 执行异步调用的委托本身,把委托本身传递进去的原因在下面可以看到。
IAsyncResult ar = dlgt.BeginInvoke(3000,
out threadId,
new AsyncCallback(CallbackMethod),
dlgt );
Console.WriteLine("Press Enter to close application.");
Console.ReadLine();
}
//回调函数必须严格的遵照AsyncCallback委托的签名。
static void CallbackMethod(IAsyncResult ar) {
//在这里,上面那个dlgt作为参数的作用得到了体现,原来它就是为了完成对EndInvoke的调用啊。
AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;
// 通过对EndInvoke的调用获取返回值。
string ret = dlgt.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
}
我想,通过前面的代码应该足以把BeginInvoke和EndInvoke的常规说清楚,因为这些代码不仅来自于MSDN2003,而且它确实很有代表性。在这里我只是想给大家提醒一下曾经被我忽略而又恰恰十分有趣的部分。
还记得第四种方案里的异步调用语句吗?
IAsyncResult ar = dlgt.BeginInvoke(3000, out threadId, new AsyncCallback(CallbackMethod), dlgt );
这个调用的第三个参数是一个委托。而这个委托的调用是需要有参数提供的。看看那个CallbackMethod的声明吧:
static void CallbackMethod(IAsyncResult ar) ;
呵呵,注意到了吗?上面用红色标注的对象ar在代码中其实是同一个对象。你还记得我给你说过的BegingInvoke的奇异之处吗?它的返回值竟然是它调用的参数,当然,这个参数是偷偷传递的,你一不小心,就忽视它了。
另外,第四个参数也有很大的灵活性,具体看你怎么运用了。