開篇第一句:.net4.5中異步方法的實現遠不如看起來的那么簡單,編譯器背后代替開發人員生成了大量的代碼 做了好多事情使它看起來簡單了,這些代碼和過去實現異步操作時必須開發人員手動編寫並維護的樣板代碼的數量類似。此外,編譯器生成的代碼會在 .NET Framework 中調用庫代碼,再次代替開發人員完成更多的工作。要獲得正確的思維模式並使用這一模式做出合適的開發決策,重要的一點是了解編譯器代替您生成了哪些內容。
.net4.5中定義了大量的異步方法***Async(例如Windows.System.Launcher.LaunchUriAsync() )這些方法的返回類型是IAsyncInfo或者繼承自該接口的IAsyncAction、IAsyncActionWithProgress、IAsyncOperation 和 IAsyncOperationWithProgress等(注意在Windows 8 Consumer Preview版本中這些方法都改為熱啟動的,異步方法會在將操作返回到調用程序之前啟動操作,所以無需***Async.Start()等操作)。
WinRT 異步模型的核心接口依托於 IAsyncInfo 而構建。該核心接口可以定義異步操作(例如,當前狀態、取消操作的功能和失敗操作的錯誤等)的屬性。用戶可以通過該類型的返回值得到異步方法***Async的當前狀態Status,標識Id,以及該異步方法失敗的錯誤信息ErrorCode,並且可以取消和關閉該操作。
View Code
public interface IAsyncInfo { // 摘要: // Gets a string that describes an error condition of the asynchronous operation. // // 返回結果: // The error string. Exception ErrorCode { get; } // // 摘要: // Gets the handle of the asynchronous operation. // // 返回結果: // The handle. uint Id { get; } // // 摘要: // Gets a value that indicates the status of the asynchronous operation. // // 返回結果: // The status of the operation. AsyncStatus Status { get; } // 摘要: // Cancels the asynchronous operation. void Cancel(); // // 摘要: // Closes the asynchronous operation. void Close(); }
但該特定接口缺少對異步操作來說無疑是至關重要的功能:當操作完成時,通過回調通知監聽器以及異步操作運行時進度報告。該功能有意地划分到了四個依賴 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(); }
例如我們可以這么使用:(更多信息請參考http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/03/26/windows.aspx)
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;
}
});
};}
但為了處理一個異步調用,我們就需要編寫大量代碼,手動處理完成回調,手動封送回 UI 線程,明確檢查完成狀態等等。於是一個關鍵字await應運而生。有了await我們可以這么寫:
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; }
}
注意async關鍵字,使用 async 關鍵字標記方法,會導致 C# 或 Visual Basic 編譯器使用狀態機重新編寫該方法的實施。借助此狀態機,編譯器可以在該方法中插入多個中斷點,以便該方法可以在不阻止線程的情況下,掛起和恢復其執行。這些中斷點不會隨意地插入。它們只會在您明確使用 await 關鍵字的位置插入. 詳細信息請參考:http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/04/30/winrt-await.aspx
總結一下:
async關鍵字的作用:
1.告訴編譯器你想在該方法中使用await關鍵字(只可以在標有async關鍵字的方法或者lambda表達式中使用await關鍵字),如果標有async方法中沒有await,編譯器編譯時會發出警告。而編譯器碰到使用aysnc標記的方法會使用狀態機重新編寫該方法的實施。這樣該方法在await關鍵字出現的地方可以在不阻止線程的情況下,掛起和恢復其執行。
2.告訴編譯器來自該操作的任何異常將得到傳播或作為結果返回(通過 GetResult),如果該方法返回值是Task或者Task<TResult>,就意味着任何返回值或者該方法中未處理的異常都會存儲到返回的Task中;對於一個返回值是Void的方法來說,這意味着 任何異常都會傳播到調用者的上下文中. 也就是上邊例子中所展示的。
await關鍵字的作用:
告訴編譯器在標記async的方法中插入一個掛起/恢復執行的點。
Task Task〈TResult>和WinRT異步方法之間的互換:
在構建 WinRT 庫時,該庫中所有全局公開的異步操作都會強類型化為返回這四個接口之一。與此相對,從 .NET 庫公開的新異步操作遵循基於任務的異步模式 (TAP)。對於不返回結果的操作返回 Task,而對於返回結果的操作則返回 Task<TResult>。Task 和 Task<TResult> 不會實施這些 WinRT 接口,公共語言運行時 (CLR) 也不會暗中掩飾它們的差異(對於某些類型會如此,例如 WinRT Windows.Foundation.Uri 類型和 BCL System.Uri類型)。而是我們需要明確地從一種模式轉換到另一種。Task和Task<>也可以使用AsAsyncAction, AsAsyncOperation<>轉化為IAsyncAction、IAsyncActionWithProgress、IAsyncOperation 和 IAsyncOperationWithProgress等接口方法,后台實現機制為適配器模式。
Task<int> t;
t.AsAsyncAction();
t.AsAsyncOperation<int>
另外這些異步方法還可以通過AsTask和AsTask<>等方法轉化成為Task或者Task<>,后台使用擴展方法實現,類似於Linq的實現方式。
IAsyncOperationWithProcess<int, string> op;
op.AsTask<int, string>
詳細了解請參考以下鏈接。
Reference:
http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/03/26/windows.aspx
http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/04/30/winrt-await.aspx
http://blogs.msdn.com/b/windowsappdev_cn/archive/2012/06/22/net-winrt.aspx
http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx
http://www.microsoft.com/en-us/download/details.aspx?id=19957
http://msdn.microsoft.com/zh-CN/async
關於TAP
在對比了早期IAsyncResult模式也叫APM模式和基於事件的異步模式Event-based Asynchronous PatternEAP,基於任務的異步模式Task-based Asynchronous Pattern 有很大的優勢。對比一下代碼:
public class MyClass
{
public int Read(byte [] buffer, int offset, int count);
}
The APM counterpart to this method would expose the following two methods:
public class MyClass
{
public IAsyncResult BeginRead(
byte [] buffer, int offset, int count,
AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
}
The EAP counterpart would expose the following set of types and members:
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; }
}
The TAP counterpart would expose the following single method:
public class MyClass
{
public Task<int> ReadAsync(byte [] buffer, int offset, int count);
}
詳細參考:http://www.microsoft.com/en-us/download/details.aspx?id=19957
另外推薦兩個很好的post,關於.net4中引入的異步編程任務並行庫(Task Parallel Library)以及CLR4中線程池的內部實現。
http://www.codeproject.com/Articles/152765/Task-Parallel-Library-1-of-n
http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx
