什么是异步编程模型
异步编程模型(Asynchronous Programming Model,简称APM)是C#1.1支持的一种实现异步操作的编程模型,虽然已经比较“古老”了,但是依然可以学习一下的。通过对APM的学习,我总结了以下三点:
1. APM的本质是使用委托和线程池来实现异步编程的。
2. 实现APM的关键是要实现IAsyncResult接口。
3. 实现了APM的类都会定义一对形如BeginXXX()和EndXXX()的方法,例如,FileStream类定义了BeginRead()方法和EndRead()方法,可以实现异步读取文件内容。
下面我们就通过具体的代码来实现异步编程模型。
实现异步编程模型
1. 实现IAsyncResult接口
IAsyncResult接口是C#类库中定义的一个接口,表示异步操作的状态,具体介绍可以查看MSDN。
1 public interface IAsyncResult2 {3 object AsyncState { get; }4 5 WaitHandle AsyncWaitHandle { get; }6 7 bool CompletedSynchronously { get; }8 9 bool IsCompleted { get; }
10 }
上面的代码是IAsyncResult接口声明的四个属性:
1. AsyncState属性是一个用户定义的对象,包含异步操作状态信息。例如,当我们调用FileStream类的BeginRead()方法进行异步读取文件内容时,传入的最后一个参数对应的就是AsyncState属性。
2. AsyncWaitHandle属性主要的作用是阻塞当前线程来等待异步操作完成。WaitHandle抽象类,有一个很重要的派生类ManualResetEvent。
3. CompletedSynchronously属性比较特别,用来判断异步操作是否是同步完成(这个有点儿绕~)。
4. IsCompleted属性就比较简单了,用来判断异步操作是否完成,true表示已完成,false表示还未完成。
在实现IAsyncResult接口时,我们主要会用到AsyncState,IsCompleted和AsyncWaitHandle属性。
1 /// <summary>2 /// CalculatorAsyncResult<T>类,实现了IAsyncResult接口3 /// </summary>4 /// <typeparam name="T"></typeparam>5 public class CalculatorAsyncResult<T> : IAsyncResult6 {7 private ManualResetEvent _waitHandle;8 9 private object _asyncState;10 11 private bool _completedSynchronously;12 13 private bool _isCompleted;14 15 //我们传入的异步回调方法16 private AsyncCallback _asyncCallback;17 18 //保存异步操作返回结果19 public T CalulatorResult { get; set; }20 21 public static CalculatorAsyncResult<T> CreateCalculatorAsyncResult(Func<T> work, AsyncCallback asyncCallback, object obj)22 {23 var asyncResult = new CalculatorAsyncResult<T>(obj, asyncCallback, false, false);24 25 asyncResult.ExecuteWork(work);26 27 return asyncResult;28 }29 30 public CalculatorAsyncResult(object obj, AsyncCallback asyncCallback, bool completedSynchronously, bool isCompleted)31 {32 _waitHandle = new ManualResetEvent(false);33 34 _asyncState = obj;35 36 _completedSynchronously = completedSynchronously;37 38 _isCompleted = isCompleted;39 40 _asyncCallback = asyncCallback;41 }42 43 public object AsyncState44 { 45 get { return _asyncState; } 46 }47 48 public WaitHandle AsyncWaitHandle49 {50 get{ return _waitHandle; }51 }52 53 public bool CompletedSynchronously54 {55 get { return _completedSynchronously; }56 }57 58 public bool IsCompleted59 {60 get { return _isCompleted; }61 }62 63 public void Wait()64 {65 _waitHandle.WaitOne();66 }67 68 /// <summary>69 /// 调用异步回调方法70 /// </summary>71 private void InvokeAsyncCallback()72 {73 _isCompleted = true;74 75 if (_waitHandle != null)76 {77 _waitHandle.Set();78 }79 80 //调用我们传入的异步回调方法81 _asyncCallback(this);82 }83 84 /// <summary>85 /// 执行异步工作86 /// </summary>87 /// <param name="work"></param>88 public void ExecuteWork(Func<T> work)89 {90 if(_asyncCallback != null)91 {92 Task<T> task = Task.Factory.StartNew<T>(work);93 94 task.ContinueWith(t => 95 {96 CalulatorResult = t.Result;97 98 InvokeAsyncCallback();99 });
100 }
101 else
102 {
103 _isCompleted = true;
104
105 if(_waitHandle != null)
106 {
107 _waitHandle.Set();
108 }
109 }
110 }
111 }
2. 定义BeginXXX()和EndXXX()方法
下面就来定义我们自己的APM接口和具体实现类,编写BeginXXX()和EndXXX()方法。
1 /// <summary>2 /// 异步计算接口3 /// </summary>4 /// <typeparam name="T"></typeparam>5 public interface ICalculator<T>6 {7 IAsyncResult BeginAdd(T x, T y, AsyncCallback asyncCallback, Object obj);8 9 T EndAdd(IAsyncResult ar);
10 }
1 /// <summary>2 /// 异步计算接口实现类3 /// </summary>4 public class Calculator : ICalculator<double>5 {6 public IAsyncResult BeginAdd(double x, double y, AsyncCallback asyncCallback, Object obj)7 {8 return CalculatorAsyncResult<double>.CreateCalculatorAsyncResult(delegate { return Add(x, y); }, asyncCallback, obj);9 }
10
11 public double EndAdd(IAsyncResult ar)
12 {
13 var calculatorAsyncResult = (CalculatorAsyncResult<double>)(ar);
14
15 calculatorAsyncResult.Wait();
16
17 return calculatorAsyncResult.CalulatorResult;
18 }
19
20 /// <summary>
21 /// 计算方法
22 /// </summary>
23 /// <param name="x"></param>
24 /// <param name="y"></param>
25 /// <returns></returns>
26 protected double Add(double x, double y)
27 {
28 Console.WriteLine("Async thread(id={0}) begins.\n", Thread.CurrentThread.ManagedThreadId);
29
30 Console.WriteLine("Async thread(id={0}) is calculating...\n", Thread.CurrentThread.ManagedThreadId);
31
32 Thread.Sleep(3000);
33
34 var r = x + y;
35
36 Console.WriteLine("Async thread(id={0}) ends.\n", Thread.CurrentThread.ManagedThreadId);
37
38 return r;
39 }
40 }
3. 获取异步操作结果
APM提供了四种获取异步操作的结果方式供我们选择:
1. 通过IAsyncResult的AsyncWaitHandle属性,调用它的WaitOne()方法使调用线程阻塞来等待异步操作完成再调用EndXXX()方法来获取异步操作结果。
2. 在调用BeginXXX()方法的线程上调用EndXXX()方法来获取异步操作结果。这种方式也会阻塞调用线程(阻塞原理同方式1,具体在上面的代码中有体现)。
3. 轮询IAsyncResult的IsComplete属性,当异步操作完成后再调用EndXXX()方法来获取异步操作结果。
4. 使用 AsyncCallback委托来指定异步操作完成时要回调的方法,在回调方法中调用EndXXX()方法来获取异步操作结果。
在上述的四种方式中,只有第四种方式是完全不会阻塞调用线程的,所以多数情况下我们都会选择回调的方式来获取异步操作结果。
1 public class Program2 {3 public static double result = 0;4 5 static void Main(string[] args)6 {7 Console.WriteLine("Main thread(id={0}) begins.\n", Thread.CurrentThread.ManagedThreadId);8 9 var calculator = new Calculator();
10
11 Console.WriteLine("Main thread(id={0}) invokes BeginAdd() function.\n", Thread.CurrentThread.ManagedThreadId);
12
13 calculator.BeginAdd(1, 2, Callback, calculator);
14
15 Console.WriteLine("Main thread(id={0}) is sleeping...\n", Thread.CurrentThread.ManagedThreadId);
16
17 Thread.Sleep(5000);
18
19 Console.WriteLine("The calculating result of async operation is {0}.\n", result);
20
21 Console.WriteLine("Main thread(id={0}) ends.\n", Thread.CurrentThread.ManagedThreadId);
22 }
23
24 /// <summary>
25 /// 我们定义的回调方法
26 /// </summary>
27 /// <param name="ar"></param>
28 public static void Callback(IAsyncResult ar)
29 {
30 var calculator = (Calculator)(ar.AsyncState);
31
32 result = calculator.EndAdd(ar);
33 }
34 }
运行结果:
至此,我们已经完整地实现了APM异步编程模型,从运行结果中我们可以得出,通过回调的方式来获取异步操作结果是完全不会阻塞调用线程的。
总结
1. 实现APM的关键是实现IAsyncResult接口。在IAsyncResult实现类中,需要使用线程池来异步地执行操作,在操作完成之后,再调用传入的回调方法来返回操作结果。
2. 实现了APM的类中都会定义一对BeginXXX()和EndXXX()方法,开始异步操作,结束异步操作并返回异步操作结果。
3. 获取异步操作结果有四种方式,但是只有回调方式是完全不会阻塞调用线程的,其他的都会阻塞调用线程。