天天看點

基于Task的異步模式的定義

傳回該系列目錄《基于Task的異步模式--全面介紹》

命名,參數和傳回類型

在TAP(Task-based Asynchronous Pattern)中的異步操作的啟動和完成是通過一個單獨的方法來表現的,是以隻有一個方法要命名。這與IAsyncResult模式或者APM(Asynchronous Programming Model,異步程式設計模型)模式形成對比,後者必須要有開始方法名和結束方法名;還與基于事件(event-based)的異步模式(EAP)不同,它們要求方法名以Async為字尾,而且要求一個或多個事件,事件句柄委托類型和派生自Event參數的類型。TAP中的異步方法使用“Async”字尾命名,跟在操作名稱的後面(例如MethodNameAsync)。TAP中的異步方法傳回一個Task類型或者Task<TResult>,基于相應的同步方法是否分别傳回一個void或者TResult類型。

比如,思考下面的“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,本文檔後面的會更深入地讨論。

表現

初始化異步操作

在傳回結果的任務之前,基于TAP異步方法允許同步地處理少量的工作。這項工作應保持在所需的最低數量,執行如驗證參數和啟動異步操作的操作。很可能從使用者界面線程将調用異步方法,是以所有長時間運作的異步方法的同步前期部分工作可能會損害響應能力。很有可能同時将啟動多個異步方法,是以所有長時間運作的異步方法的同步前期部分工作可能會推遲啟動其他異步操作,進而減少并發的好處。

在某些情況下,完成操作所需的工作量小于異步啟動操作需要的工作量(例如,從流中讀取資料,這個讀取操作可以被已經緩沖在記憶體中的資料所滿足)。在這種情況下,操作可能同步完成,傳回一個已經完成的任務。

異常

一個異步方法隻應該直接捕獲一個MethodNameAsync 調用時抛出的異常以響應用法錯誤。對于其他所有的錯誤,在異步方法執行期間發生的異常應該配置設定給傳回的任務。這種情況是在Task傳回之前,異步方法同步完成下發生的。一般地,一個Task至多包含一個異常。然而,對于一個Task表示多個操作(如,Task.WhenAll)的情況,單個Task也會關聯多個異常。

【*每個.Net設計指南都指出,一個用法錯誤可以通過改變調用方法的碼來避免。比如,當把null作為一個方法的參數傳遞時,錯誤狀态就會發生,錯誤條件通常被表示為ArgumentNullException,開發者可以修改調用碼來確定null沒有傳遞過。換言之,開發者可以并且應該確定用法錯誤從來沒有在生産代碼中發生過】。

目标環境

異步執行的發生取決于TAP方法的實作。TAP方法的開發人員可能選擇線上程池上執行工作負載,也可能選擇使用異步 I/O實作它,因而沒有被綁定到大量操作執行的線程上,也可以選擇在特定的線程上運作,如UI線程,或者其他一些潛在的上下文。甚至可能是這種情況,TAP方法沒有東西執行,簡單傳回一個在系統中其他地方情況發生的Task(如Task<TData>表示TData到達一個排隊的資料結構)。

TAP方法的調用者也可能阻塞等待TAP方法的完成(通過在結果的Task上同步地等待),或者利用延續在異步操作完成時執行附加代碼。延續建立者在延續代碼執行的地方有控制權。這些延續代碼要麼通過Task類(如ContinueWith)顯示地建立,要麼使用語言支援隐式地建立在延續代碼之上(如C#中的“await”)。

Task狀态

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>對象的進度,使得比快速提供進度的異步實作更廉價,并且允許進度的消費者決定如何以及在哪裡最好地處理資訊(例如進度執行個體本身可以選擇在一個捕獲的同步上下文上收集回調函數和引發事件)。

IProgreee<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一樣。

如果您認為這篇文章還不錯或者有所收獲,您可以通過右邊的“打賞”功能 打賞我一杯咖啡【物質支援】,也可以點選右下角的【好文要頂】按鈕【精神支援】,因為這兩種支援都是我繼續寫作,分享的最大動力!

作者:tkb至簡

來源:http://farb.cnblogs.com/

聲明:原創部落格請在轉載時保留原文連結或者在文章開頭加上本人部落格位址,如發現錯誤,歡迎批評指正。凡是轉載于本人的文章,不能設定打賞功能,如有特殊需求請與本人聯系!

已将所有贊助者統一放到單獨頁面!簽名處隻保留最近10條贊助記錄!檢視贊助者清單

衷心感謝打賞者的厚愛與支援!也感謝點贊和評論的園友的支援!
打賞者 打賞金額 打賞日期
微信:匿名 10.00 2017-08-03
2017-08-04
5.00 2017-06-15
支付寶:一個名字499***@qq.com 2017-06-14
16.00 2017-04-08
支付寶:向京劉 2017-04-13
2017-003-08
2017-03-08
支付寶:lll20001155 2017-03-03
支付寶:她是一個弱女子 2017-03-02

繼續閱讀