返回該系列目錄《基於Task的異步模式--全面介紹》
Tasks和異步編程模型APM(Tasks and the Asynchronous Programming Model)
從APM到Tasks
APM模式依賴兩個對應的方法來表示一個異步操作:BeginMethodName和EndMethodName。在高級別,begin方法接受的參數和相應的同步方法MethodName的參數是一樣的,而且還接受一個AsyncCallback和一個object state。begin方法然后返回IAsyncResult,IAsyncResult從它的AsyncState屬性返回傳遞給begin方法的object state。異步操作完成時,IAsyncResult的IsCompleted屬性會開始返回true,且會設置它的AsyncWaitHandle屬性。而且,如果begin方法的AsyncCallback參數是非空的,那么會調用callback,且將它傳給從begin方法返回的相同的IAsyncResult。當異步操作確實完成時,會使用EndMethodName方法連接該操作,檢索任何結果或者強制產生的異常傳播。
由於APM模式結構的本質,構建一個APM的包裝器來將它暴露為一個TAP實現是相當容易的。實際上,.Net Framework 4 以TaskFactory.FromAsync的形式提供了轉化的幫助路線。
思考.Net 中的Stream類和BeginRead/EndRead 方法,它們都代表了同步的Read方法的APM對應版本:
public int Read( byte [] buffer, int offset, int count); … public IAsyncResult BeginRead( byte [] buffer, int offset, int count, AsyncCallback callback, object state); public int EndRead(IAsyncResult asyncResult);
利用FromAsycn,可實現該方法的TAP包裝器:
public static Task<int> ReadAsync( this Stream stream, byte [] buffer, int offset, int count) { if (stream == null) throw new ArgumentNullException(“stream”); return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null); }
這個使用了FromAsync的實現和下面的具有同樣效果:
public static Task<int> ReadAsync( this Stream stream, byte [] buffer, int offset, int count) { if (stream == null) throw new ArgumentNullException(“stream”); var tcs = new TaskCompletionSource<int>(); stream.BeginRead(buffer, offset, count, iar => { try { tcs.TrySetResult(stream.EndRead(iar)); } catch(OperationCanceledException) { tcs.TrySetCanceled(); } catch(Exception exc) { tcs.TrySetException(exc); } }, null); return tcs.Task; }
從Tasks到APM
對於現有的基礎設施期望代碼實現APM模式的場合,能夠采取TAP實現以及在期待TAP實現的地方使用它也是很重要的。幸好有了tasks的組合性,以及Task本身實現IAsyncResult的事實,使用一個簡單的幫助函數就可以實現了(這里展示的是一個Task<TResult>的擴展,但幾乎相同的函數可能用於非泛型的Task):
public static IAsyncResult AsApm<T>( this Task<T> task, AsyncCallback callback, object state) { if (task == null) throw new ArgumentNullException(“task”); var tcs = new TaskCompletionSource<T>(state); task.ContinueWith(t => { if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions) else if (t.IsCanceled) tcs.TrySetCanceled(); else tcs.TrySetResult(t.Result); if (callback != null) callback(tcs.Task); }, TaskScheduler.Default); return tcs.Task; }
現在,想一個有TAP實現的場合:
public static Task<string> DownloadStringAsync(Uri url);
且我們需要提供APM實現:
public IAsyncResult BeginDownloadString( Uri url, AsyncCallback callback, object state); public string EndDownloadString(IAsyncResult asyncResult);
可以通過下面代碼實現:
public IAsyncResult BeginDownloadString( Uri url, AsyncCallback callback, object state) { return DownloadStringAsync(url).AsApm(callback, state); } public string EndDownloadString(IAsyncResult asyncResult) { return ((Task<string>)asyncResult).Result; }
Tasks和基於事件的異步模式EAP(Event-based Asynchronous Pattern)
基於事件的異步模式依賴於一個返回void的實例MethodNameAsync方法,接收和同步方法MethodName方法相同的參數,並且要實例化異步操作。實例異步操作之前,事件句柄使用相同實例上的事件注冊,然后觸發這些事件來提供進度和完成通知。事件句柄一般都是自定義的委托類型,該委托類型利用了派生自ProgressChangedEventArgs或AsyncCompletedEventArgs的事件參數類型。
包裝一個EAP實現更復雜一些,因為該模式本身牽扯了比APM模式更多的變量和更少的結構。為了演示,接下來包裝一個DownloadStringAsync方法。DownloadStringAsync接受一個Uri參數,為了上報多個進度上的統計數據,下載時會觸發DownloadProgressChanged 事件,完成時會觸發DownloadStringCompleted 事件。最終結果是一個包含在指定Uri的頁面內容的字符串。
public static Task<string> DownloadStringAsync(Uri url) { var tcs = new TaskCompletionSource<string>(); var wc = new WebClient(); wc.DownloadStringCompleted += (s,e) => { if (e.Error != null) tcs.TrySetException(e.Error); else if (e.Cancelled) tcs.TrySetCanceled(); else tcs.TrySetResult(e.Result); }; wc.DownloadStringAsync(url); return tcs.Task; }
Tasks和等待句柄(WaitHandlers)
從WaitHandlers到Tasks
高級的開發人員可能會發現,WaitHandle 設置時,自己利用 WaitHandles 和線程池的 RegisterWaitForSingleObject 方法進行異步通知,然而這本質上不是一個異步模式 。我們可以包裝RegisterWaitForSingleObject來啟用WaitHandle之上的任何異步等待的基於task的選擇:
public static Task WaitOneAsync(this WaitHandle waitHandle) { if (waitHandle == null) throw new ArgumentNullException("waitHandle"); var tcs = new TaskCompletionSource<bool>(); var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, delegate { tcs.TrySetResult(true); }, null, -1, true); var t = tcs.Task; t.ContinueWith(_ => rwh.Unregister(null)); return t; }
使用那些之前演示的構建於Task之上的數據結構的技巧,相似地,構建一個不依賴WaitHandles且完全以Task的角度工作的異步信號燈(semaphore)也是可能的。事實上,.Net 4.5中的SemaphoreSlim 類型暴露了開啟這個的WaitAsync方法。
比如,之前提到的System.Threading.Tasks.Dataflow.dll中的BufferBlock<T>類型可以這樣使用:
static SemaphoreSlim m_throttle = new SemaphoreSlim(N, N); static async Task DoOperation() { await m_throttle.WaitAsync(); … // do work m_throttle.Release (); }
從Tasks到WaitHandlers
如之前提到的,Task類實現了IAsyncResult,該IAsyncResult的實現暴露了一個返回WaitHandle的AsycnWaitHandle屬性,此WaitHandle是在Task完成時設置的。照這樣,獲得一個Task的WaitHandle可以像下面這樣實現:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;