开篇第一句:.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,并且可以取消和关闭该操作。
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