Docs-.NET-.NET 指南-非同步程式設計模式:實現基於任務的非同步模式
ylbtech-Docs-.NET-.NET 指南-非同步程式設計模式:實現基於任務的非同步模式 |
1.返回頂部 |
實現基於任務的非同步模式
可使用以下三種方式實現基於任務的非同步模式 (TAP):使用 Visual Studio 中的 C# 和 Visual Basic 編譯器、手動實現或編譯器和手動方法相結合。以下各節詳細地討論了每一種方法。可以使用 TAP 模式實現計算密集型和 I/O 密集型非同步操作。工作負載部分介紹了各種型別的操作。
生成 TAP 方法
使用編譯器
自 .NET Framework 4.5 起,任何歸於async
關鍵字(Visual Basic 中的Async
TResult
,並且編譯器確保此結果是通過生成的任務物件獲得。同樣,未在方法的主體中處理的任何異常都會被封送處理為輸出任務並導致生成的任務結束以TaskStatus.Faulted狀態結束。此規則的異常發生在OperationCanceledException(或派生型別)未得到處理時,在這種情況下生成的任務以
手動生成 TAP 方法
你可以手動實現 TAP 模式以更好地控制實現。編譯器依賴從System.Threading.Tasks名稱空間公開的公共外圍應用和System.Runtime.CompilerServices名稱空間中支援的型別。如要自己實現 TAP,你需要建立一個TaskCompletionSource<TResult>物件、執行非同步操作,並在操作完成時,呼叫SetResult、SetException、SetCanceled方法,或呼叫這些方法之一的Try
版本。手動實現 TAP 方法時,需在所表示的非同步操作完成時完成生成的任務。例如:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state) { var tcs = new TaskCompletionSource<int>(); stream.BeginRead(buffer, offset, count, ar => { try { tcs.SetResult(stream.EndRead(ar)); } catch (Exception exc) { tcs.SetException(exc); } }, state); return tcs.Task; }
混合方法
你可能發現手動實現 TAP 模式、但將實現核心邏輯委託給編譯器的這種方法很有用。例如,當你想要驗證編譯器生成的非同步方法之外的實參時,可能需要使用這種混合方法,以便異常可以轉義到該方法的直接呼叫方而不是通過System.Threading.Tasks.Task物件被公開:
C#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 goes here return value; }
這種委託有用的另一種情況是:你在實施快速路徑優化並想返回快取的任務時。
工作負載
你可將計算密集型和 I/O 密集型非同步操作作為 TAP 方法實現。但是,當 TAP 方法從庫中公開地公開時,應僅向涉及 I/O 密集型操作的工作負載提供這種方法(它們也可能涉及計算,但不是應僅僅是計算)。如果是純粹的計算密集型方法,應只公開為同步實現。然後,使用它的程式碼可能會選擇是將同步方法呼叫包裝到任務中以將工作解除安裝到另一執行緒,還是實現並行。如果方法是 I/O 密集型,應只公開為非同步實現。
計算密集型任務
System.Threading.Tasks.Task類非常適合表示計算密集型操作。預設情況下,它利用ThreadPool類中的特殊支援來提供有效的執行,還對執行非同步計算的時間、地點和方式提供重要控制。
你可以通過以下方式生成計算密集型任務:
-
在 .NET Framework 4 中,使用TaskFactory.StartNew方法,這種方法接受非同步執行委託(通常是Action<T>或Func<TResult>)。如果你提供Action<T>委託,該方法會返回表示非同步執行該委託的System.Threading.Tasks.Task物件。如果你提供Func<TResult>委託,該方法會返回System.Threading.Tasks.Task<TResult>物件。StartNew方法的過載接受一個取消標記(CancellationToken)、任務建立選項(TaskCreationOptions)和一個任務計劃程式(TaskScheduler),它們都對計劃和任務執行提供細粒度控制。定目標到當前任務計劃程式的工廠例項可用作Task類的靜態屬性 (Factory);例如:
Task.Factory.StartNew(…)
。 -
在 .NET Framework 4.5 及更高版本(包括 .NET Core 和 .NET Standard)中,使用靜態Task.Run方法作為TaskFactory.StartNew的快捷方式。你可以使用Run來輕鬆啟動針對執行緒池的計算密集型任務。在 .NET Framework 4.5 及更高版本中,這是用於啟動計算密集型任務的首選機制。僅當需要更細化地控制任務時,才直接使用
StartNew
。 -
想要分別生成並計劃任務時,請使用
Task
型別或Start
方法的建構函式。公共方法必須僅返回已開始的任務。 -
使用Task.ContinueWith方法的過載。此方法建立一項在另一任務完成時已排好計劃的新任務。某些ContinueWith過載接受一個取消標記、延續選項和一個任務計劃程式,以更好地控制計劃和執行延續任務。
-
使用TaskFactory.ContinueWhenAll和TaskFactory.ContinueWhenAny方法。這些方法會在提供的全部任務或任意一組任務完成時建立已計劃的新任務。這些方法還提供了過載,用於控制這些任務的計劃和執行。
在計算密集型任務中,如果系統在開始執行任務之前收到取消請求,則它可以防止執行已計劃的任務。同樣,如果你提供一個取消標記(CancellationToken物件),則可以將標記傳遞給監視該標記的非同步程式碼。你也可以將此標記提供給先前提過的方法(如StartNew
或Run
),以便Task
執行時也能監視該標記。
例如,請考慮使用呈現影象的非同步方法。任務的主體可以輪詢取消標記,如果在呈現過程中收到取消請求,程式碼可提前退出。此外,如果呈現啟動之前收到取消請求,你需要阻止呈現操作:
C#internal 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); }
如果滿足下列條件之一,則計算密集型任務以Canceled狀態結束:
-
取消請求通過CancellationToken物件到達,該物件在任務轉換到
StartNew
狀態前,作為建立方法的自變數(例如Run
或Running)提供。 -
OperationCanceledException異常在此類任務的主體內未得到處理,該異常包含傳給該任務的同一CancellationToken,並且該標記顯示已請求取消操作。
如果另一個異常在任務的主體內未得到處理,則此任務以Faulted狀態結束,並且任何等待該任務或訪問其結果的嘗試都將引發異常。
I/O 密集型任務
若要建立一個不應由執行緒直接支援其全部執行的任務,請使用TaskCompletionSource<TResult>型別。此型別公開一個返回關聯Task例項的Task<TResult>屬性。此任務的生命週期是由TaskCompletionSource<TResult>方法控制的,比如SetResult、SetException、SetCanceled以及它們的TrySet
變形。
假設你想建立一個將在指定時間段後完成的任務。例如,你可能想延遲使用者介面中的活動。System.Threading.Timer類已提供在指定時間段後以非同步方式呼叫委託的能力,並且你可以通過使用TaskCompletionSource<TResult>將Task<TResult>前端放在計時器上,例如:
C#public static Task<DateTimeOffset> Delay(int millisecondsTimeout) { TaskCompletionSource<DateTimeOffset> tcs = null; Timer timer = null; timer = new Timer(delegate { timer.Dispose(); tcs.TrySetResult(DateTimeOffset.UtcNow); }, null, Timeout.Infinite, Timeout.Infinite); tcs = new TaskCompletionSource<DateTimeOffset>(timer); timer.Change(millisecondsTimeout, Timeout.Infinite); return tcs.Task; }
從 .NET Framework 4.5 開始,Task.Delay方法正是為此而提供的,並且你可以在另一個非同步方法內使用它。例如,若要實現非同步輪詢迴圈:
C#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,因此你可以為僅返回任務的 I/O 密集型方法使用泛型TaskCompletionSource<TResult>物件。為了做到這一點,你可以使用具有虛擬TResult
(Boolean是一個很好的預設選項,但是如果你擔心Task使用者將其向下轉換成Task<TResult>,那麼你可以轉而使用私有TResult
型別)。例如,上一個示例中的Delay
方法返回現有時間和所產生的偏移量(Task<DateTimeOffset>
)。如果結果值是不必要的,則可對該方法進行如下改寫(注意對TrySetResult的返回型別的更改和實參的更改):
public static Task<bool> Delay(int millisecondsTimeout) { TaskCompletionSource<bool> tcs = null; Timer timer = null; timer = new Timer(delegate { timer.Dispose(); tcs.TrySetResult(true); }, null, Timeout.Infinite, Timeout.Infinite); tcs = new TaskCompletionSource<bool>(timer); timer.Change(millisecondsTimeout, Timeout.Infinite); return tcs.Task; }
計算密集型和 I/O 密集型混合任務
非同步方法不只侷限於計算密集型或 I/O 密集型操作,還可以是兩者的結合。事實上,多個非同步操作通常組合成較大的混合操作。例如,請考慮前面示例中的RenderAsync
方法,該方法執行計算密集型操作以根據某些輸入imageData
呈現影象。此imageData
可能來自你非同步訪問的 Web 服務:
public async Task<Bitmap> DownloadDataAndRenderImageAsync( CancellationToken cancellationToken) { var imageData = await DownloadImageDataAsync(cancellationToken); return await RenderAsync(imageData, cancellationToken); }
此示例還演示瞭如何通過多個非同步操作使單個取消標記執行緒化。有關詳細資訊,請參閱使用基於任務的非同步模式中的取消用法部分。
請參閱
2、2.返回頂部 |
3.返回頂部 |
4.返回頂部 |
5.返回頂部 |
6.返回頂部 |
作者:ylbtech 出處:http://ylbtech.cnblogs.com/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。 |