在.NET Framework 4.5中,C#編譯器實現了TAP。任何標有async關鍵字的方法都是異步方法,編譯器會使用TAP執行必要的轉換從而異步地實現方法。這樣的方法應該返回Task或者Task<TResult>類型。在后者的案例中,方法體應該返回一個TResult,且編譯器將確保通過返回的Task<TResult>是可利用的。相似地,方法體內未經處理的異常會被封送到輸出的task,造成返回的Task以Faulted的狀態結束。一個例外是如果OperationCanceledException(或派生類型)未經處理,那么返回的Task會以Canceled狀態結束。
開發者可以手動地實現TAP,就像編譯器那樣或者更好地控制方法的實現。編譯器依賴來自System.Threading.Tasks命名空間暴露的公開表面區域(和建立在System.Threading.Tasks之上的System.Runtime.CompilerServices中支持的類型),還有對開發者直接可用的功能。當手動實現TAP方法時,開發者必須保證當異步操作完成時,完成返回的Task。
在編譯器生成的實現中混合核心邏輯的實現,對于手動實現TAP通常是很有用的。比如這種情況,為了避免方法直接調用者產生而不是通過Task暴露的異常,如:
public Task<int> MethodAsync(string input){ if (input == null) throw new ArgumentNullException("input"); return MethodAsyncInternal(input);}PRivate async Task<int> MethodAsyncInternal(string input){ … // code that uses await}
參數應該在編譯器生成的異步方法之外改變,這種委托有用的另一種場合是,當一個“快速通道”優化可以通過返回一個緩存的task來實現的時候。
計算受限和I/O受限的異步操作可以通過TAP方法實現。然而,當TAP的實現從一個庫公開暴露時,應該只提供給包含I/O操作的工作負荷(它們也可以包含計算,但不應該只包含計算)。如果一個方法純粹受計算限制,它應該只通過一個異步實現暴露,消費者然后就可以為了把該任務卸載給其他的線程的目的來選擇是否把那個同步方法的調用包裝成一個Task,并且/或者來實現并行。
Task類最適合表示計算密集型操作。默認地,為了提供有效的執行操作,它利用了.Net線程池中特殊的支持,同時也對異步計算何時,何地,如何執行提供了大量的控制。
生成計算受限的tasks有幾種方法。
思考下面的渲染圖片的異步方法。task體可以獲得cancellation token為的是,當渲染發生的時候,如果一個撤銷請求到達后,代碼可能過早退出。而且,如果一個撤銷請求在渲染開始之前發生,我們也可以阻止任何的渲染。
public Task<Bitmap> RenderAsync( ImageData data, CancellationToken cancellationToken){ return Task.Run(() => { var bmp = new Bitmap(data.Width, data.Height); for(int y=0; y<data.Height; y++) { cancellationToken.ThrowIfCancellationRequested(); for(int x=0; x<data.Width; x++) { … // render pixel [x,y] into bmp } } return bmp; }, cancellationToken);}
如果下面的條件至少一個是正確的,計算受限的tasks會以一個Canceled狀態的結束:
如果該Task體中有另外一個未經處理的異常,那么該Task就會以Faulted的狀態結束,同時在該task上等待的任何嘗試或者訪問它的結果都將導致拋出異常。
使用TaskCompletionSource<TResult>類型創建的Tasks不應該直接被全部執行的線程返回。TaskCompletionSource<TResult>暴露了一個返回相關的Task<TResult>實例的Task屬性。該task的生命周期通過TaskCompletionSource<TResult>實例暴露的方法控制,換句話說,這些實例包括SetResult, SetException, SetCanceled, 和它們的TrySet* 變量。
思考這樣的需求,創建一個在特定的時間之后會完成的task。比如,當開發者在UI場景中想要延遲一個活動一段時間時,這可能使有用的。.NET中的System.Threading.Timer類已經提供了這種能力,在一段特定時間后異步地調用一個委托,并且我們可以使用TaskCompletionSource<TResult>把一個Task放在timer上,例如:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout){ var tcs = new TaskCompletionSource<DateTimeOffset>(); new Timer(self => { ((IDisposable)self).Dispose(); tcs.TrySetResult(DateTimeOffset.UtcNow); }).Change(millisecondsTimeout, -1); return tcs.Task;}
在.Net 4.5中,Task.Delay()就是為了這個目的而生的。比如,這樣的一個方法可以使用到另一個異步方法的內部,以實現一個異步的輪訓循環:
public static async Task Poll( Uri url, CancellationToken cancellationToken, iprogress<bool> progress){ while(true) { await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); bool success = false; try { await DownloadStringAsync(url); success = true; } catch { /* ignore errors */ } progress.Report(success); }}
沒有TaskCompletionSource<TResult>的非泛型副本。然而,Task<TResult>派生自Task,因而,泛型的TaskCompletionSource<TResult>可以用于那些 I/O受限的方法,它們都利用一個假的TResult源(Boolean是默認選擇,如果開發者關心Task向下轉型的Task<TResult>的消費者,那么可以使用一個私有的TResult類型)僅僅返回一個Task。比如,開發的之前的Delay方法是為了順著產生的Task<DateTimeOffset>返回當前的時間。如果這樣的 一個結果值是不必要的,那么該方法可以通過下面的代碼取而代之(注意返回類型的改變和TrySetresult參數的改變):
public static Task Delay(int millisecondsTimeout){ var tcs = new TaskCompletionSource<bool>(); new Timer(self => { ((IDisposable)self).Dispose(); tcs.TrySetResult(true); }).Change(millisecondsTimeout, -1); return tcs.Task;}
異步方法不是僅僅受限于計算受限或者I/O受限的操作,而是可以代表這兩者的混合。實際上,通常情況是不同性質的多個異步操作被組合在一起生成更大的混合操作。比如,思考之前的RenderAsync方法,該方法基于一些輸入的ImageData執行一個計算密集的操作來渲染一張圖片。該ImageData可能來自于一個我們異步訪問的Web服務:
public async Task<Bitmap> DownloadDataAndRenderImageAsync( CancellationToken cancellationToken){ var imageData = await DownloadImageDataAsync(cancellationToken); return await RenderAsync(imageData, cancellationToken);}
這個例子也展示了一個單獨的CancellationToken是如何通過多個異步操作被線程化的。
新聞熱點
疑難解答