作為模式,只是一種大家認可的經驗,模式可以作為大家交流的詞匯而存在。下面我們就要介紹幾種異步編程模式,AMP、EAP和TAP。當然,法無定法,在我們理解的基礎上,我們可以根據具體情況適度修改。下面介紹的只是在通常情況下的兩種模式會是以什么樣子的形式出現。
一 模型簡介
1.APM 異步編程模型
這種模式的特征是一些成對出現的方法,分別以Begin和End作為前綴。
2.EAP 基於事件的異步模式
這個模式通常用於面向UI的組件,其中這些組件必須和進度報告和取消操作關聯起來。方法有Async前綴。這種模式通常實現起來更為復雜,並且會帶來一些額外開銷,比如要由線程池線程轉換到UI線程。
3.TAP 基於任務的模式
二 AMP模型
AMP的實現過程可以分為3個步驟,前兩個步驟很明顯,就是
1.編寫BeginXXX方法,例如
public IAsyncResult BeginWork(int sleepyTime, AsyncCallback callback, object state)
最后兩個參數是模式必須的,一個代表完成時的回調,一個代表要傳遞給回調的對象。因為AsyncCallback的定義是這樣的
public delegate void AsyncCallback ( IAsyncResult ar )
所以你沒辦法直接給回調方法傳遞自己想要的參數,只能通過state來間接的傳遞給回調方法。
2.編寫EndXXX方法
注意,EndXXX方法不等於回調方法,EndFoo方法應該是在回調方法里被調用來獲取執行結果,這是剛接觸的學習者常弄混的概念。它返回的結果應該和同步的XXX方法是一樣的。下面展示了一個IAsyncResult的End方法的通常實現方式:
public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; }
由於異步會立即返回,因此立即返回的對象是一個實現了IAsyncResult接口的對象,不是我們真正所需要的結果。那么AMP如何獲取我們真正想要的結果呢?有下面這四種形式:
-
1.從應用程序的主線程調用EndXXX方法,阻止應用程序執行,直到操作完成之后再繼續執行。比如:
IAsyncResult ar = foo.BeginWork(null, null); var result = foo.EndWork(ar);
-
2.使用 AsyncWaitHandle 來阻止應用程序執行,直到一個或多個操作完成之后再繼續執行。
-
3.按以下方式輪詢操作完成狀態:定期檢查 IsCompleted 屬性,操作完成后調用 End方法。
-
4.使用 AsyncCallback 委托來指定操作完成時要調用的方法。
上面這幾種方式都和一個實現了IAsyncResult接口的對象有關,所以第三步就是
3.編寫一個實現IAsyncResult接口的類把BeginXXX和EndXXX這兩個方法關聯起來。
需要注意的是,前面三種方式主要利用了內核同步對象,會引起線程阻塞。我們主要關注第四種以回調的形式,這才是APM最常用的方式。
我們打算自己實現以下IAsyncResult來更好的說明這四種方式, 在動手實現之前,先看看IAsyncResult接口的定義
public interface IAsyncResult { object AsyncState { get; } WaitHandle AsyncWaitHandle { get; } bool CompletedSynchronously { get; } bool IsCompleted { get; } }
實現IAsyncResult
public delegate T Func<T>(); public class SimpleAsyncResult<T> : IAsyncResult { // All of the ordinary async result state. private volatile int _isCompleted; // 0==not complete, 1==complete. private readonly ManualResetEvent _asyncWaitHandle; private readonly AsyncCallback _callback; private readonly object _asyncState; private Exception _exception; private T _result; public SimpleAsyncResult( Func<T> work, AsyncCallback callback, object state) { _callback = callback; _asyncState = state; _asyncWaitHandle = new ManualResetEvent(false); RunWorkAsynchronously(work); } public bool IsCompleted { get { return (_isCompleted == 1); } } public WaitHandle AsyncWaitHandle { get { return _asyncWaitHandle; } } public object AsyncState { get { return _asyncState; } } // Runs the work on the thread pool, capturing exceptions, // results, and signaling completion. private void RunWorkAsynchronously(Func<T> work) { ThreadPool.QueueUserWorkItem(delegate { try { _result = work(); } catch (Exception e) { _exception = e; } finally { // Signal completion in the proper order: _isCompleted = 1; _asyncWaitHandle.Set(); if (_callback != null) _callback(this); } }); } public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; } }
如何使用我們上面所編寫的類來實現異步方法:
public class SimpleAsyncOperation { public int Work(int sleepyTime) { Thread.Sleep(sleepyTime); return new Random().Next(); } public IAsyncResult BeginWork( int sleepyTime, AsyncCallback callback, object state) { return new SimpleAsyncResult<int>( delegate { return Work(sleepyTime); }, callback, state ); } public int EndWork(IAsyncResult asyncResult) { SimpleAsyncResult<int> simpleResult = asyncResult as SimpleAsyncResult<int>; if (simpleResult == null) throw new ArgumentException("Bad async result."); return simpleResult.End(); } }
構造函數的參數包含了一個Func<T>委托,這個委托表示需要通過異步方式來完成的工作。我們在私有方法RunWorkAsynchronously中使用線程池來實現了異步運行,並且是在一個try塊中運行。如果執行成功,那么返回值將被保存在對象的_result對象中;如果執行在過程中拋出一個異常,那么這個異常將被保存在_exception域中。我們要確保這個異常不會超出Catch塊的范圍,否則將在線程池上導致一個未處理異常,這將使進程崩潰。
可以看到,EndWork的實現里調用simpleResult.End()方法,而simpleResult.End的實現里首先判斷是否完成了,如果沒完成則調用內核對象阻塞線程。
public T End() { // Wait for the work to finish, if it hasn't already. if (_isCompleted == 0) { _asyncWaitHandle.WaitOne(); _asyncWaitHandle.Close(); } // Propagate any exceptions or return the result. if (_exception != null) throw _exception; return _result; }
所以正確的調用EndWork的時機應該是在回調方法里,而不應該直接調用。當然,在我們的實現里為了簡單起見,直接實例化了一個ManualResetEvent類型的_asyncWaitHandle,因此就算在回調方法里調用了EndWork方法,仍然會創建一個內核對象。
_isCompleted = 1; _asyncWaitHandle.Set(); if (_callback != null) _callback(this);
更高效的實現是只有在直接調用了AsyncWaitHandle屬性或者在isCompleted = 1之前調用了EndXXX方法時,才應該初始化_asyncWaitHandle。
.NET Framework中常用的一些APM
1.委托
所有的委托類型除了提供Invoke方法外,還要提供一個BeginInvoke方法。但是應盡量避免使用,異步委托的實現並不高效。
2.System.IO.Stream
基類Stream類中為BeginWrtie,BeginRead提供了一個默認的實現,因此所有它的子類都自動有相應的方法。大多數的Stream,比如經常用到的FileStream,都重寫了這個默認的實現,依賴非托管的Windows Overlapped I/O獲取更高的效率。
3.System.Net.WebRequest
該類並沒有實現BeginGetResponse,BeginGetRequestStream這些方法,因此將拋出一個NotImplementedException,但它的子類如HttpWebRequest等,都提供了實現。
總結:
APM是用於更低層次框架和庫組件,在這些環境中注重的是靈活性而不是完成事件的發生方式(當然,我們通常都是用的是回調的方式)。通常一般應用層開發人員並不需要關心細節和細粒度的控制,而是希望簡單的能夠在某個事件完成后調用一些方法,或者更方便的回到GUI線程,這時EAP是一種不錯的模式。
下一篇介紹EAP和UI線程以及UI編程模式的相關知識,過渡到IOS開發了。