C#異步編程模型
什麽是異步編程模型
異步編程模型(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#異步編程模型