1. 程式人生 > >C#異步編程模型

C#異步編程模型

操作 null 參數 編程模型 spa zh-cn 完全 times ras

什麽是異步編程模型

異步編程模型(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 IAsyncResult
 2 {
 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> : IAsyncResult
  6 {
  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 AsyncState
 44     { 
 45         get { return _asyncState; } 
 46     }
 47 
 48     public WaitHandle AsyncWaitHandle
 49     {
 50         get{ return _waitHandle; }
 51     }
 52 
 53     public bool CompletedSynchronously
 54     {
 55         get { return _completedSynchronously; }
 56     }
 57 
 58     public bool IsCompleted
 59     {
 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.", Thread.CurrentThread.ManagedThreadId);
29 
30         Console.WriteLine("Calculating...");
31 
32         Thread.Sleep(3000);
33 
34         var r = x + y;
35 
36         Console.WriteLine("Async thread(id={0}) ends.", 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 Program
 2 {
 3     public static double result = 0;
 4 
 5     static void Main(string[] args)
 6     {
 7         Console.WriteLine("Main thread(id={0}) begins.", Thread.CurrentThread.ManagedThreadId);
 8 
 9         var calculator = new Calculator();
10 
11         calculator.BeginAdd(1, 2, Callback, calculator);
12 
13         Thread.Sleep(5000);
14 
15         Console.WriteLine("Calculating result is {0}.", result);
16 
17         Console.WriteLine("Main thread(id={0}) ends.", Thread.CurrentThread.ManagedThreadId);
18     }
19 
20     /// <summary>
21     /// 我們定義的回調方法
22     /// </summary>
23     /// <param name="ar"></param>
24     public static void Callback(IAsyncResult ar)
25     {
26         var calculator = (Calculator)(ar.AsyncState);
27 
28         result = calculator.EndAdd(ar);
29     }
30 }

運行結果:

技術分享

至此,我們已經完整地實現了APM異步編程模型,從運行結果中我們可以得出,通過回調的方式來獲取異步操作結果是完全不會阻塞調用線程的。

總結

1. 實現APM的關鍵是實現IAsyncResult接口。在IAsyncResult實現類中,需要使用線程池來異步地執行操作,在操作完成之後,再調用傳入的回調方法來返回操作結果。

2. 實現了APM的類中都會定義一對BeginXXX()和EndXXX()方法,開始異步操作,結束異步操作並返回異步操作結果。

3. 獲取異步操作結果有四種方式,但是只有回調方式是完全不會阻塞調用線程的,其他的都會阻塞調用線程。

C#異步編程模型