現在小米2手機都已經4核了,更別說PC機了 。
多核,并發 ,異步編程是不可阻擋的趨勢, 各種語言也都逐漸加入了對異步編程的支持。
異步編程的本質是將主線程某些任務委托給某個線程處理,主線程繼續無阻塞運行, 等另外一個線程處理完了再將結果通知主線程(在主線程中調用某個主線程的結果處理方法)。
本來很好理解的概念,但是因為匿名函數(Lambda)導致的閉包和多線程以及編譯器在背后做了很多事情,導致我們很難深入的理解它。
下文轉自http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/04/30/winrt-await.aspx
在最近發布的使用 Windows 運行時中異步性來始終保持應用程序能夠快速流暢地運行這篇博文中,包含了一些如何在 C# 和 Visual Basic 中使用 await 關鍵字的示例,允許開發人員在使用 WinRT 異步操作的同時,保持和推導良好的控制流。
在接下來的博文中,我將更加深入地介紹 await 在 WinRT 中的工作原理。這些知識將幫助您更輕松地推導使用 await 的代碼,進而幫助您編寫更出色的 Metro 風格應用程序。
首先,我們先來審視一下沒有 await 的情況。
基礎知識回顧
WinRT 中的所有異步功能全部源自同一個接口:IAsyncInfo。
public interface IAsyncInfo
{
AsyncStatus Status { get; }
HResult ErrorCode { get; }
uint Id { get; }
void Cancel();
void Close();
}
WinRT 中的每個異步操作都需要實施此接口,該接口可提供執行異步操作所需的基本功能,查詢其標識和狀態,并請求其取消。但該特定接口缺少對異步操作來說無疑是至關重要的功能:當操作完成時,通過回調通知監聽器。該功能有意地劃分到了四個依賴 IAsyncInfo 的其他接口中,而 WinRT 中的每個異步操作都需要實施以下四個接口之一:
public interface IAsyncAction : IAsyncInfo
{
AsyncActionCompletedHandler Completed { get; set; }
void GetResults();
}
public interface IAsyncOperation<TResult> : IAsyncInfo
{
AsyncOperationCompletedHandler<TResult> Completed { get; set; }
TResult GetResults();
}
public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
AsyncActionProgressHandler<TProgress> Progress { get; set; }
void GetResults();
}
public interface IAsyncOperationWithProgress<TResult, TProgress> : IAsyncInfo
{
AsyncOperationWithProgressCompletedHandler<TResult, TProgress> Completed { get; set; }
AsyncOperationProgressHandler<TResult, TProgress> Progress { get; set; }
TResult GetResults();
}
這四個接口支持帶有/不帶結果,以及帶有/不帶進度報告的全部組合。所有這些接口都會公開一個 Completed 屬性,該屬性可以設置為在操作完成時調用的委派。您只能設置一次該委派,而如果該委派在操作完成后設置,它將通過處理操作完成和分配委派之間先后順序的實施,立即加入計劃或得到調用。
現在,我們假設要實施一個帶有 XAML 按鈕的 Metro 風格應用程序,單擊該按鈕可以將某些任務加入 WinRT 線程池,以執行某種大計算量的操作。當該任務完成時,按鈕的內容將根據操作的結果進行更新。
我們應該如何實施此功能呢?WinRT ThreadPool 類公開了一種可以在池中異步運行任務的方法:
public static IAsyncAction RunAsync(WorkItemHandler handler);
我們可以使用此方法將大計算量的工作加入隊列,以避免當在任務運行時阻止 UI 線程:
private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
int result = 0;
var op = ThreadPool.RunAsync(delegate { result = Compute(); });
}
我們現在已經成功地將工作從 UI 線程分流到了池中,但我們如何獲知工作何時完成呢?RunAsync 會返回一個 IAsyncAction,因此我們可以使用一個完成處理程序來接收該通知,并運行續體作為響應:
private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
int result = 0;
var op = ThreadPool.RunAsync(delegate { result = Compute(); });
op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
{
btnDoWork.Content = result.ToString(); // bug!
};
}
現在,當加入 ThreadPool 隊列的異步操作完成時,Completed 處理程序將得到調用,并嘗試將結果存儲到按鈕中。很不幸,這一操作目前無法成功執行。Completed 處理程序不太可能在 UI 線程中得到調用,但是,為了修改 btnDoWork.Content,該處理程序需要在 UI 線程中運行(如果無法實現,則將導致一個錯誤代碼為“RPC_E_WRONG_THREAD”的異常)。為此,我們可以使用與 UI 相關的CoreDispatcher 對象將調用封送回所需的位置:
private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
int result = 0;
var op = ThreadPool.RunAsync(delegate { result = Compute(); });
op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate
{
btnDoWork.Content = result.ToString();
});
};
}
現在,該應用可以正常工作了。但如果 Compute 方法拋出異常會怎樣?如果某人調用返回自 ThreadPool.RunAsync 的IAsyncAction 中的 Cancel 又會怎樣?我們的 Completed 處理程序需要處理 IAsyncAction 可能會以下列三種終端狀態之一結束的事實:Completed、Error 或 Canceled:
private void btnDoWork_Click(object sender, RoutedEventArgs e)
{
int result = 0;
var op = ThreadPool.RunAsync(delegate { result = Compute(); })
op.Completed = delegate(IAsyncAction asyncAction, AsyncStatus asyncStatus)
{
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate
{
switch (asyncStatus)
{
case AsyncStatus.Completed:
btnDoWork.Content = result.ToString();
break;
case AsyncStatus.Error:
btnDoWork.Content = asyncAction.ErrorCode.Message;
break;
case AsyncStatus.Canceled:
btnDoWork.Content = "A task was canceled";
break;
}
});
};
}
為了處理一個異步調用,我們就需要編寫大量代碼;如果我們需要連續執行多個異步操作,情形可想而知。如果我們能通過某種方法替代這種繁復的代碼編寫工作,豈不是非常美妙?
private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
try
{
int result = 0;
await ThreadPool.RunAsync(delegate { result = Compute(); });
btnDoWork.Content = result.ToString();
}
catch (Exception exc) { btnDoWork.Content = exc.Message; }
}
這段代碼的功能與前一段代碼完全相同。但我們不再需要手動處理完成回調。我們不再需要手動封送回 UI 線程。我們不再需要明確檢查完成狀態。并且我們不再需要顛倒控制流,這意味著我們可以輕而易舉地擴展至更多操作,例如,循環執行多個計算和 UI 更新:
private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
try
{
for(int i=0; i<5; i++)
{
int result = 0;
await ThreadPool.RunAsync(delegate { result = Compute(); });
btnDoWork.Content = result.ToString();
}
}
catch (Exception exc) { btnDoWork.Content = exc.Message; }
}
請回想一下手動使用 IAsyncAction 時,為實現上述功能必須編寫的代碼量。這就是 C# 和 Visual Basic 中新的 async/await 關鍵字的魔力。好消息是您可以親手編寫這些代碼,它們并非難以捉摸的魔法。在本博文余下的篇幅中,我們將帶您深入了解這一功能的后臺原理。
編譯器轉換
使用 async 關鍵字標記方法,會導致 C# 或 Visual Basic 編譯器使用狀態機重新編寫該方法的實施。借助此狀態機,編譯器可以在該方法中插入多個中斷點,以便該方法可以在不阻止線程的情況下,掛起和恢復其執行。這些中斷點不會隨意地插入。它們只會在您明確使用await 關鍵字的位置插入:
private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
...
await something; // <-- potential method suspension point
...
}
當您等待未完成的異步操作時,編譯器生成的代碼可確保與該方法相關的所有狀態(例如,局部變量)封裝并保留在堆中。然后,該函數將返回到調用程序,允許在其運行的線程中執行其他任務。當所等待的異步操作在稍后完成時,該方法將使用保留的狀態恢復執行。
任何公開 await 模式的類型都可以進行等待。該模式主要由一個公開的 GetAwaiter 方法組成,該方法會返回一個提供IsCompleted、OnCompleted 和 GetResult 成員的類型。當您編寫以下代碼時:
編譯器會生成一段代碼,在實例 something 上使用這些成員來檢查該對象是否已完成(通過 IsCompleted),如果未完成,則掛接一個續體(通過 OnCompleted),并在該任務最終完成時進行回調以繼續執行。該操作完成后,來自該操作的任何異常將得到傳播或作為結果返回(通過 GetResult)。因此,當您編寫以下代碼時:
private async void btnDoWork_Click(object sender, RoutedEventArgs e)
{
...
await ThreadPool.RunAsync(delegate { result = Compute(); });
...
}
編譯器會將其轉換為類似如下的代碼:
private class btnDoWork_ClickStateMachine : IAsyncStateMachine
{
// Member fields for preserving locals and other necessary state
int $state;
TaskAwaiter<string> $awaiter;
int result;
...
// Method that moves to the next state in the state machine
public void MoveNext()
{
// Jump table to get back to the right statement upon resumption
switch (this.$state)
{
...
case 2: goto Label2;
...
}
...
// Expansion of await ...;
var tmp1 = ThreadPool.RunAsync(delegate { this.result = Compute(); });
this.$awaiter = tmp1.GetAwaiter();
if (!this.$awaiter.IsCompleted)
{
this.$state = 2;
this.$awaiter.OnCompleted(MoveNext);
return;
Label2:
}
this.$awaiter.GetResult();
...
}
}
對于 btnDoWork_Click 方法,編譯器會生成一個包含 MoveNext 方法的狀態機類。對 MoveNext 的每次調用都會恢復btnDoWork_Click 方法的執行,直到遇到下一個針對某項未完成任務的 await 語句,或直到該方法結束,二者取其先。當編譯器生成的代碼發現未完成的所等待實例時,它會使用狀態變量標記當前的位置,安排方法在所等待實例完成時繼續執行,然后返回。當所等待實例最終完成時,系統將再次調用 MoveNext 方法,并跳轉至上次執行中斷的位置。
編譯器事實上并不關心 IAsyncAction 是否在此處等待。它只關心是否有可供綁定的正確模式。當然,您已經看到了 IAsyncAction接口的實施,并且知道其中不包含編譯器所期待的 GetAwaiter 方法。那么,為什么它能夠成功進行編譯并運行呢?為了更好地理解其中的原因,您需要首先了解 .NET Task 和 Task<TResult> 類型(該框架用于表示異步操作的核心),及其與 await 的關系。
轉換為任務
.NET Framework 4.5 包含支持等待 Task 和 Task<TResult> 實例(Task<TResult> 派生自 Task)所需的全部類型和方法。Task 和 Task<TResult> 均公開 GetAwaiter 實例方法,并分別返回 TaskAwaiter 和 TaskAwaiter<TResult> 類型,這兩種類型均公開可滿足 C# 和 Visual Basic 編譯器需要的 IsCompleted、OnCompleted 和 GetResult 成員。IsCompleted 會返回一個布爾值,指示在訪問該屬性的時刻,任務是否已經完成執行。OnCompleted 會將任務掛接到一個續體委派,該委派會在任務完成時得到調用(如果當 OnCompleted 得到調用時該任務已經完成,該續體委派將加入異步執行計劃)。GetResult 會在以TaskStatus.RanToCompletion 狀態結束的情況下返回任務的結果(針對非泛型 Task 類型,返回 void);在以TaskStatus.Canceled 狀態結束的情況下,拋出 OperationCanceledException;并在以 TaskStatus.Faulted 狀態結束的情況下,拋出導致任務失敗的任何異常。
如果希望某種自定義類型支持等待,我們可以選擇兩種主要的方法。一種方法是針對自定義的可等待類型手動實施完整的 await 模式,提供一個返回自定義等待程序類型的 GetAwaiter 方法,該等待程序類型知道如何處理續體和異常傳播等等。第二種實施該功能的方法是將自定義類型轉換為任務,然后只需依靠對等待任務的內置支持來等待特殊類型。我們來探究一下后一種方法。
.NET Framework 包含一種名為 TaskCompletionSource<TResult> 的類型,可以讓此類轉換更加直觀。TaskCompletionSource<TResult> 會創建一個 Task<TResult> 對象,并向您提供用于直接控制相應的任務將在何時、以何種狀態結束的 SetResult、SetException 和 SetCanceled 方法。因此,您可以將 TaskCompletionSource<TResult> 用作填充或代理來表示某些其他異步操作,例如,某種 WinRT 異步操作。
我們暫時假設您不知道自己可以直接等待 WinRT 操作。那么,您將如何來實現這些功能呢?
IAsyncOperation<string> op = SomeMethodAsync();
string result = await ...; // need something to await
您可以創建一個 TaskCompletionSource<TResult>,將其用作代理來表示該 WinRT 異步操作,然后等待相應的任務。我們來嘗試一下。首先,我們需要實例化一個 TaskCompletionSource<TResult>,以便等待其 Task:
IAsyncOperation<string> op = SomeMethodAsync();
var tcs = new TaskCompletionSource<TResult>();
...
string result = await tcs.Task;
然后,如同我們在稍早前的示例中看到如何手動使用 WinRT 異步操作的 Completed 處理程序一樣,我們需要向該異步操作掛接一個回調,以便獲知其何時完成:
IAsyncOperation<string> op = SomeMethodAsync();
var tcs = new TaskCompletionSource<TResult>();
op.Completed = delegate
{
...
};
string result = await tcs.Task;
然后在該回調中,我們需要將 IAsyncOperation<TResult> 的完成狀態傳輸給該任務:
IAsyncOperation<string> op = SomeMethodAsync();
var tcs = new TaskCompletionSource<TResult>();
op.Completed = delegate
{
switch(operation.Status)
{
AsyncStatus.Completed:
tcs.SetResult(operation.GetResults());
break;
AsyncStatus.Error:
tcs.SetException(operation.ErrorCode);
break;
AsyncStatus.Canceled:
tcs.SetCanceled();
break;
}
};
string result = await tcs.Task;
就是這樣。即使處理程序在該操作已經完成后才注冊,WinRT 異步操作也可確保 Completed 得到適當的調用,因此我們在注冊該處理程序時,無需針對其與該操作完成的先后順序進行特殊處理。WinRT 異步操作還負責在操作完成后將引用下降至 Completed 處理程序,因此我們無需進行任何特殊處理,以便在處理程序得到調用時將 Completed 設置為 null;事實上,Completed 處理程序只能設置一次,這意味著如果您再次嘗試設置它,將引發一個錯誤。
借助此方法,WinRT 異步操作的完成狀態和代表任務的完成狀態之間將建立起一對一的映射:
終端 AsyncStatus | 轉換為 TaskStatus | 等待內容 |
Completed | RanToCompletion | 返回操作的結果(或 void) |
Error | Faulted | 拋出失敗操作的異常 |
Canceled | Canceled | 拋出 OperationCanceledException |
當然,如果我們在每次希望等待某個 WinRT 異步操作時都必須編寫這段用于處理此 await 操作的樣板代碼,代碼很快將變得冗長而乏味。作為優秀的程序開發人員,我們可以將該樣板封裝到一個方法中,以便反復使用。我們可以編寫一個將 WinRT 異步操作轉換為任務的擴展方法:
public static Task<TResult> AsTask<TResult>(
this IAsyncOperation<TResult> operation)
{
var tcs = new TaskCompletionSource<TResult>();
operation.Completed = delegate
{
switch(operation.Status)
{
AsyncStatus.Completed:
tcs.SetResult(operation.GetResults());
break;
AsyncStatus.Error:
tcs.SetException(operation.ErrorCode);
break;
AsyncStatus.Canceled:
tcs.SetCanceled();
break;
}
};
return tcs.Task;
}
借助該擴展方法,我現在可以編寫如下的代碼:
IAsyncOperation<string> op = SomeMethodAsync();
string result = await op.AsTask();
甚至可以簡化為:
string result = await SomeMethodAsync().AsTask();
這樣就好多了。當然,此類可重用的 AsTask 功能對于在 C# 和 Visual Basic 中使用 WinRT 的任何人來說都必不可少,因此您實際上不需要編寫自己的實施:.NET 4.5 中已經內置了此類方法。System.Runtime.WindowsRuntime.dll 程序集包含了用于 WinRT 異步接口的這些擴展方法:
namespace System
{
public static class WindowsRuntimeSystemExtensions
{
// IAsyncAction
public static Task AsTask(
this IAsyncAction source);
public static Task AsTask(
this IAsyncAction source,
CancellationToken cancellationToken);
// IAsyncActionWithProgress
public static Task AsTask<TProgress>(
this IAsyncActionWithProgress<TProgress> source);
public static Task AsTask<TProgress>(
this IAsyncActionWithProgress<TProgress> source,
IProgress<TProgress> progress);
public static Task AsTask<TProgress>(
this IAsyncActionWithProgress<TProgress> source,
CancellationToken cancellationToken);
public static Task AsTask<TProgress>(
this IAsyncActionWithProgress<TProgress> source,
CancellationToken cancellationToken,
IProgress<TProgress> progress);
// IAsyncOperation
public static Task<TResult> AsTask<TResult>(
this IAsyncOperation<TResult> source);
public static Task<TResult> AsTask<TResult>(
this IAsyncOperation<TResult> source,
CancellationToken cancellationToken);
// IAsyncOperationWithProgress
public static Task<TResult> AsTask<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> source);
public static Task<TResult> AsTask<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> source,
IProgress<TProgress> progress);
public static Task<TResult> AsTask<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> source,
CancellationToken cancellationToken);
public static Task<TResult> AsTask<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> source,
CancellationToken cancellationToken,
IProgress<TProgress> progress);
...
}
}
這四種接口均具有一個無參數的 AsTask 重載,與我們剛剛從頭編寫那個非常類似。此外,每種接口還具有一個接受CancellationToken 的重載。此標記是 .NET 中用于提供可組合和可合作取消的常見機制;您會向所有異步操作傳入一個標記,而當系統請求取消時,所有這些異步操作都將接到取消請求。您現在已經知道存在現成可用的此類 API,但為了說明其原理,我們還是要向您介紹一下如何構建此類 AsTask(CancellationToken) 重載。CancellationToken 提供了一個 Register 方法,該方法會接受將在請求取消時調用的委派;我們可以簡單地提供一個委派,然后通過取消請求調用 IAsyncInfo 對象中的 Cancel,并通過取消請求轉發:
public static Task<TResult> AsTask<TResult>(
this IAsyncOperation<TResult> operation,
CancellationToken cancellationToken
{
using(cancellationToken.Register(() => operation.Cancel()))
return await operation.AsTask();
}
盡管 .NET 4.5 中自帶的實施并非與此完全相同,但邏輯上基本一致。
對于 IAsyncActionWithProgress<TProgress> 和 IAsyncOperationWithProgress<TResult,TProgress>,還具有接受IProgress<TProgress> 的重載。IProgress<T> 是一種可以由方法接受以通報回進度的 .NET 接口,而 AsTask 方法只需為 WinRT 異步操作的 Progress 屬性連接委派,即可將進度信息轉發給 IProgress。同樣,我們將展示手動實施該功能的方法,以供您進行參考:
public static Task<TResult> AsTask<TResult,TProgress>(
this IAsyncOperationWithProgress<TResult> operation,
IProgress<TProgress> progress
{
operation.Progress += (_,p) => progress.Report(p);
return operation.AsTask();
}
直接等待 WinRT 異步操作
我們現在已經知道了如何創建代表 WinRT 異步操作的任務,以便可以對其進行等待。但是,如何才能直接等待 WinRT 操作呢?換言之,最好能夠編寫如下的代碼:
await SomeMethodAsync().AsTask();
但對于不需要提供 CancellationToken 或 IProgress<T> 的情況,避免編寫調用 AsTask 的代碼豈不更好?
當然,我們已在本博文的開頭提到,這是可以實現的。請回憶一下編譯器如何期待發現一個返回適當等待程序類型的 GetAwaiter 方法。如前所述,System.Runtime.WindowsRuntime.dll 中的 WindowsRuntimeSystemExtensions 類型包含針對四種 WinRT 異步接口的此類 GetAwaiter 擴展方法:
namespace System
{
public static class WindowsRuntimeSystemExtensions
{
...
public static TaskAwaiter GetAwaiter(
this IAsyncAction source);
public static TaskAwaiter<TResult> GetAwaiter<TResult>(
this IAsyncOperation<TResult> source);
public static TaskAwaiter GetAwaiter<TProgress>(
this IAsyncActionWithProgress<TProgress> source);
public static TaskAwaiter<TResult> GetAwaiter<TResult, TProgress>(
this IAsyncOperationWithProgress<TResult, TProgress> source);
}
}
請注意來自這些方法的返回類型:TaskAwaiter 或 TaskAwaiter<TResult>。這些方法均利用了框架內置的現有任務等待程序。根據已經掌握的 AsTask 相關知識,您可能已經猜到了這些功能的實施方法。框架中的實際實施基本上與此完全相同:
public static TaskAwaiter GetAwaiter(
this IAsyncAction source)
{
return source.AsTask().GetAwaiter();
}
這意味著這兩行代碼將引發完全相同的行為:
await SomeMethodAsync().AsTask();
await SomeMethodAsync();
自定義等待行為
如前所述,TaskAwaiter 和 TaskAwaiter<TResult> 可提供滿足編譯器對等待程序的期待所需的全部成員:
bool IsCompleted { get; }
void OnCompleted(Action continuation);
TResult GetResult(); //returns void on TaskAwaiter
其中最有趣的成員為 OnCompleted,它將在所等待的操作完成時,負責調用續體委派。OnCompleted 提供特殊封送行為,以確保續體委派在正確的位置執行。
在默認情況下,當任務等待程序的 OnCompleted 得到調用時,會通知當前的 SynchronizationContext,SynchronizationContext 是代碼執行環境的抽象表示。在 Metro 風格應用程序的 UI 線程中,SynchronizationContext.Current 會返回一個內部 WinRTSynchronizationContext 類型的實例。SynchronizationContext 會提供一個虛擬的 Post 方法,該方法會接受一個委派,并在上下文的適當位置執行該委派;WinRTSynchronizationContext 封裝了一個 CoreDispatcher,并使用其 RunAsync 將委派異步調用回 UI 線程(我們稍早前已在本博文中手動實施了該功能)。當所等待的任務完成時,委派將作為 Post 傳遞給 OnCompleted,以便在調用 OnCompleted時捕獲的當前 SynchronizationContext 中執行。正是該機制允許您在 UI 邏輯中使用 await 編寫代碼,而無需擔心是否能封送回正確的線程:任務的等待程序將為您處理妥當。
當然,在某些情況下,您可能不希望執行這種默認的封送行為。此類情況多在庫中出現:許多類型的庫不關心操作 UI 控件及運行其自身的線程,因此從性能的角度來考慮,避免與跨線程封送相關的開銷將不無裨益。為了適應希望禁用這種默認封送行為的代碼,Task 和Task<TResult> 提供了 ConfigureAwait 方法。ConfigureAwait 接受布爾值 continueOnCapturedContext 參數:傳遞 True 表示使用默認行為,傳遞 False 表示系統不需要將委派的調用強制封送回原始上下文,而是在系統認為適當的任意位置執行該委派。
因此,如果您希望在不強迫剩余的代碼返回 UI 線程執行的情況下等待某個 WinRT 操作,請編寫以下任一代碼作為替換:
或者:
await SomeMethodAsync().AsTask();
您可以編寫:
await SomeMethodAsync().AsTask()
.ConfigureAwait(continueOnCapturedContext:false);
或僅編寫:
await SomeMethodAsync().AsTask().ConfigureAwait(false);
何時使用 AsTask
如果您只是希望調用某個 WinRT 異步操作并等待其完成,直接等待該 WinRT 異步操作是最為簡單和清潔的方法:
但是如果您希望獲得更強的控制力,就需要使用 AsTask。我們已經介紹了 AsTask 的一些用途:
- 通過 CancellationToken 支持取消
CancellationToken token = ...;
await SomeMethodAsync().AsTask(token);
- 通過 IProgress<T> 支持進度報告
IProgress<TProgress> progress = ...;
await SomeMethodAsync().AsTask(progress);
- 通過 ConfigureAwait 取消默認續體封送行為
await SomeMethodAsync().AsTask().ConfigureAwait(false);
在許多其他重要的情況下,AsTask 也能發揮很大的作用。
其中之一與 Task 支持多續體的功能有關。WinRT 異步操作類型僅支持注冊了 Completed 的單個委派(Completed 是一個屬性而非事件),并且該委派只能設置一次。在大多數情況下,這沒什么影響,因為您只需要等待該操作一次,例如,作為調用某個異步方法的替代方案:
您可以調用并等待某個異步對應體:
邏輯上,這將保持與使用異步對應體相同的控制流。但有時,您希望能夠掛接多個回調,或者您希望能夠多次等待同一個實例。與 WinRT 異步接口相反,Task 類型支持任意次等待和/或任意次使用其 ContinueWith 方法以支持任意次回調。因此,您可以使用 AsTask 來為您的 WinRT 異步操作獲取任務,然后將多個回調掛接到 Task 而不是直接掛接到 WinRT 異步操作。
var t = SomeMethodAsync().AsTask();
t.ContinueWith(delegate { ... });
t.ContinueWith(delegate { ... });
t.ContinueWith(delegate { ... });
AsTask 的另一個用途是處理通過 Task 或 Task<TResult> 類型運行的方法。Task.WhenAll 或 Task.WhenAny 等組合方法會通過 Task,而非 WinRT 異步接口運行。因此,如果您希望能夠調用多個 WinRT 異步操作,然后 await 他們全部或部分完成,您可以使用 AsTask 來簡化這一過程。例如,本 await 會在所提供的三個???作中的任意一個完成時完成,并返回代表其內容的 Task:
Task firstCompleted = await Task.WhenAny(
SomeMethod1Async().AsTask(),
SomeMethod2Async().AsTask(),
SomeMethod3Async().AsTask());
結論
了解到 WinRT 通過異步操作提供了如此眾多的功能的確令人倍感興奮;大量公開的此類 API 證明了響應速度對于該平臺的重要性。這也意味著開發人員急切渴求用于處理這些操作的編程模型:對于 C# 和 Visual Basic 代碼,await 和 AsTask 可謂雪中送炭。希望本博文提供的技術內幕能夠幫助您更好地理解這些功能的具體工作原理,并幫助您高效地開發 Metro 風格應用程序。