在TAP(Task-based Asynchronous Pattern)中的異步操做的啓動和完成是經過一個單獨的方法來表現的,所以只有一個方法要命名。這與IAsyncResult模式或者APM(Asynchronous Programming Model,異步編程模型)模式造成對比,後者必需要有開始方法名和結束方法名;還與基於事件(event-based)的異步模式(EAP)不一樣,它們要求方法名以Async爲後綴,並且要求一個或多個事件,事件句柄委託類型和派生自Event參數的類型。TAP中的異步方法使用「Async」後綴命名,跟在操做名稱的後面(例如MethodNameAsync)。TAP中的異步方法返回一個Task類型或者Task<TResult>,基於相應的同步方法是否分別返回一個void或者TResult類型。html
好比,思考下面的「Read」方法,它將特定數量的數據讀取到一個以特定偏移量的buffer中:編程
public class MyClass { public int Read(byte [] buffer, int offset, int count); }
這個方法對應的APM版本則有下面兩個方法:安全
public class MyClass { public IAsyncResult BeginRead(byte[] buffer, int offset, int count,AsyncCallback callback, object state); public int EndRead(IAsyncResult asyncResult); }
EAP版本對應的方法是這樣的:數據結構
public class MyClass { public void ReadAsync(byte[] buffer, int offset, int count); public event ReadCompletedEventHandler ReadCompleted; } public delegate void ReadCompletedEventHandler(object sender, ReadCompletedEventArgs eventArgs); public class ReadCompletedEventArgs: AsyncCompletedEventArgs { public int Result { get; } }
TAP對應的版本只有下面一個方法:併發
public class MyClass { public Task<int> ReadAsync(byte [] buffer, int offset, int count); }
一個基本的TAP方法的參數應該和同步方法的參數相同,且順序相同。然而,「out」和「ref」參數不聽從這個規則,而且應該避免使用它們。經過out或者ref返回的任何數據能夠做爲返回的Task<TResult>結果的一部分,能夠利用一個元組或者一個自定義數據結構容納多個值。異步
純粹致力於建立,操做,或組合的任務方法(該方法的異步目的在方法名上或者在方法上以類型命名是明確的)不須要遵循上述命名模式;這些方法一般被稱爲"組合子"。這種方法的例子包括Task. WhenAll和Task.WhenAny,本文檔後面的會更深刻地討論。async
在返回結果的任務以前,基於TAP異步方法容許同步地處理少許的工做。這項工做應保持在所需的最低數量,執行如驗證參數和啓動異步操做的操做。極可能從用戶界面線程將調用異步方法,所以全部長時間運行的異步方法的同步前期部分工做可能會損害響應能力。頗有可能同時將啓動多個異步方法,所以全部長時間運行的異步方法的同步前期部分工做可能會推遲啓動其餘異步操做,從而減小併發的好處。異步編程
在某些狀況下,完成操做所需的工做量小於異步啓動操做須要的工做量(例如,從流中讀取數據,這個讀取操做能夠被已經緩衝在內存中的數據所知足)。在這種狀況下,操做可能同步完成,返回一個已經完成的任務。函數
一個異步方法只應該直接捕獲一個MethodNameAsync 調用時拋出的異常以響應用法錯誤。對於其餘全部的錯誤,在異步方法執行期間發生的異常應該分配給返回的任務。這種狀況是在Task返回以前,異步方法同步完成下發生的。通常地,一個Task至多包含一個異常。然而,對於一個Task表示多個操做(如,Task.WhenAll)的狀況,單個Task也會關聯多個異常。spa
【*每一個.Net設計指南都指出,一個用法錯誤能夠經過改變調用方法的碼來避免。好比,當把null做爲一個方法的參數傳遞時,錯誤狀態就會發生,錯誤條件一般被表示爲ArgumentNullException,開發者能夠修改調用碼來確保null沒有傳遞過。換言之,開發者能夠而且應該確保用法錯誤歷來沒有在生產代碼中發生過】。
異步執行的發生取決於TAP方法的實現。TAP方法的開發人員可能選擇在線程池上執行工做負載,也可能選擇使用異步 I/O實現它,於是沒有被綁定到大量操做執行的線程上,也能夠選擇在特定的線程上運行,如UI線程,或者其餘一些潛在的上下文。甚至多是這種狀況,TAP方法沒有東西執行,簡單返回一個在系統中其餘地方狀況發生的Task(如Task<TData>表示TData到達一個排隊的數據結構)。
TAP方法的調用者也可能阻塞等待TAP方法的完成(經過在結果的Task上同步地等待),或者利用延續在異步操做完成時執行附加代碼。延續建立者在延續代碼執行的地方有控制權。這些延續代碼要麼經過Task類(如ContinueWith)顯示地建立,要麼使用語言支持隱式地創建在延續代碼之上(如C#中的「await」)。
Task類提供了異步操做的生命週期,該生命週期經過TaskStatus枚舉表示。爲了支持派生自Task和Task<TResult>類型的案例,以及來自調度的構建分離,Task類暴露了一個Start方法。經過public構造函數建立的Tasks被稱爲「冷」任務,在「冷」任務中,它們以非調度(non-scheduled)的TaskStatus.Created狀態開始生命週期。直到在這些實例上Start調用時,它們才促使被調度。全部在「熱」狀態開始生命週期的其餘task,意味着它們表示的異步操做已經初始化了,它們的TaskStatus是一個除Created以外的其它枚舉值。
全部從TAP方法返回的tasks確定是「熱的」。若是TAP方法內部使用一個Task的構造函數來實例化要返回的task,那麼此TAP方法必須在返回task以前在Task對象上調用Start方法。TAP方法的消費者能夠安全地假定返回的task是「熱的」,並不該該嘗試在任何返回自TAP方法的Task上調用Start。在「熱的」task上調用Start會致使InvalidOperationException (Task類自動處理這個檢查)。
TAP中的撤銷對於異步方法的實現者和異步方法的消費者都是選擇加入的。若是一個操做將要取消,那麼它會暴露
一個接受System.Threading.CancellationToken的MethodNameAsync 的重載。異步操做會監視對於撤銷請求的這個token,若是接收到了撤銷請求,能夠選擇處理該請求並取消操做。若是處理請求致使任務過早地結束,那麼從TAP方法返回的Task會以TaskStatus.Canceled狀態結束。
爲了暴露一個可取消的異步操做,TAP實現提供了在同步對應的方法的參數後接受一個CancellationToken的重載。按照慣例,該參數命名爲「cancellationToken」。
public Task<int> ReadAsync( byte [] buffer, int offset, int count, CancellationToken cancellationToken);
若是token已經請求了撤銷而且異步操做尊重該請求,那麼返回的task將會以TaskStatus.Canceled狀態結束,將會產生沒有可利用的Result,而且沒有異常。Canceled狀態被認爲是一個伴隨着Faulted和RanToCompletion 狀態的任務最終或完成的狀態。所以,Canceled 狀態的task的IsCompleted 屬性返回true。當一個Canceled 狀態的task完成時,任何用該task註冊的延續操做都會被調度或執行,除非這些延續操做經過具體的TaskContinuationOptions 用法在被建立時取消了(如TaskContinuationOptions.NotOnCanceled)。任何異步地等待一個經過語言特性使用的撤銷的task的代碼將會繼續執行而且收到一個OperationCanceledException(或派生於該異常的類型)。在該task(經過Wait 或WaitAll方法)上同步等待而阻塞的任何代碼也會繼續執行並拋出異常。
若是CancellationToken已經在接受那個token的TAP方法調用以前發出了取消請求,那麼該TAP方法必須返回一個Canceled狀態的task。然而,若是撤銷在異步操做執行期間請求,那麼異步操做不須要尊重該撤銷請求。只有因爲撤銷請求的操做完成時,返回的Task纔會以Canceled 狀態結束。若是一個撤銷被請求了,可是結果或異常仍產生了,那麼Task將會分別以RanToCompletion或 Faulted 的狀態結束。
首先,在使用異步方法的開發者心目中,那些渴望撤銷的方法,須要提供一個接受CancellationToken變量的重載。對於不可取消的方法,不該該提供接受CancellationToken的重載。這個有助於告訴調用者目標方法其實是否是可取消的。不渴望撤銷的消費者能夠調用一個接受CancellationToken的方法來把CancellationToken.None做爲提供的參數值。CancellationToken.None功能上等價於default(CancellationToken)。
一些異步操做得益於提供的進度通知,通常利用這些進度通知來更新關於異步操做進度的UI。
在TAP中,進度經過IProgress<T>接口傳遞給異步方法的名爲「progress」的參數來處理。在該異步方法調用時提供這個進度接口有助於消除來自於錯誤的用法的競爭條件,這些錯誤的用法 是由於在此操做可能錯過更新以後,事件句柄錯誤地註冊致使的。更重要的是,它使變化的進度實現可被利用,由於由消費者決定。好比,消費者肯僅僅關心最新的進度更新,或者可能緩衝全部更新,或者可能僅僅想要爲每一個更新調用一個action,或者可能想控制是否調用封送到特定的線程。全部這些可能經過使用一個不一樣的接口的實現來完成,每個接口能夠定製到特殊的消費者需求。由於有了撤銷,若是API支持進度通知,那麼TAP實現應該只提供一個IProgress<T>參數。
好比,若是咱們上面提到的ReadAsync方法能夠以迄今讀取字節數的形式能報告中間的進度,那麼進度的回調(callback)能夠是一個IProgress<int>:
public Task<int> ReadAsync( byte [] buffer, int offset, int count, IProgress<int> progress);
若是FindFilesAsync方法返回一個全部文件的列表,該列表知足一個特殊的搜索模式,那麼進度回調能夠提供完成工做的百分比和當前部分結果集的估計。它也能夠這樣處理元組,如:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync( string pattern, IProgress<Tuple<double,ReadOnlyCollection<List<FileInfo>>>> progress);
或者使用API具體的數據類型,如:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync( string pattern, IProgress<FindFilesProgressInfo> progress);
在後一種狀況,特殊的數據類型以「ProgressInfo」爲後綴。
若是TAP實現提供了接受progress參數的重載,那麼它們必須容許參數爲null,爲null的狀況下,進度不會報告。TAP實現應該同步地報告IProgress<T>對象的進度,使得比快速提供進度的異步實現更廉價,而且容許進度的消費者決定如何以及在哪裏最好地處理信息(例如進度實例自己能夠選擇在一個捕獲的同步上下文上收集回調函數和引起事件)。
Progress<T>做爲.NET Framework 4.5的一部分,是IProgress<T>的單一實現(將來會提供更多的實現)。Progress<T>聲明以下:
public class Progress<T> : IProgress<T> { public Progress(); public Progress(Action<T> handler); protected virtual void OnReport(T value); public event EventHandler<T> ProgressChanged; }
Progress<T>的實例公開了一個ProgressChanged事件,它是每次異步操做報告進度更新的時候觸發。當Progress<T>實例被實例化時,該事件在被捕獲的同步上下文上觸發(若是沒有上下文可用,那麼用默認的線程池上下文)。句柄可能會用這個事件註冊;一個單獨的句柄也可能提供給Progress實例的構造函數(這純粹是爲了方便,就像ProgressChanged 事件的事件句柄)。進度更新異步觸發是爲了事件句柄執行時避免延遲異步操做。其餘的IProgress<T>實現可能選擇使用了不一樣的語義。
有了CancellationToken和IProgress<T>參數,TAP的實現默認有4個重載函數:
public Task MethodNameAsync(…); public Task MethodNameAsync(…, CancellationToken cancellationToken); public Task MethodNameAsync(…, IProgress<T> progress); public Task MethodNameAsync(…, CancellationToken cancellationToken, IProgress<T> progress);
然而,由於它們沒有提供cancellation和progress的能力,許多TAP實現有了最短的重載的需求:
public Task MethodNameAsync(…);
若是一個實現支持cancellation或者progress但不一樣時支持,那麼TAP實現能夠提供2個重載:
public Task MethodNameAsync(…); public Task MethodNameAsync(…, CancellationToken cancellationToken); // … or … public Task MethodNameAsync(…); public Task MethodNameAsync(…, IProgress<T> progress);
若是實現同時支持cancellation和progress,那麼它能夠默認提供4個重載。然而,只有2個有效:
public Task MethodNameAsync(…); public Task MethodNameAsync(…, CancellationToken cancellationToken, IProgress<T> progress);
爲了獲得那2個遺失的重載,開發者能夠經過給CancellationToken參數傳遞CancellationToken.None(或者default(CancellationToken))和/或給progress參數傳遞null。
若是指望TAP方法的每一種用法都應該使用cancellation和/或progress,那麼不接受相關參數的重載能夠忽略。
若是TAP方法的多個重載公開了可選的cancellation和/或progress,那麼不支持cancellation和/或progress的重載的表現應該像支持他們的重載已經傳遞了CancellationToken.None和null分別給cancellation和progress同樣。