将BeginXXX/EndXXX的APM模式代码转为async异步方法只需要利用TaskFactory类的FromAsync方法即可,我们以介绍APM时提到的HttpWebRequest为例:
public Task<WebResponse> GetResponseAsync(WebRequest client) { return Task<WebResponse>.Factory.FromAsync(client.BeginGetResponse, client.EndGetResponse, null); }TaskFactory的FromAsync方法中使用TaskCompletionSource<T>来构造Task对象。
封装EAP模式的代码要比APM麻烦一些,我们需要手动构造TaskCompletionSource对象(代码来自,手打的)。
WebClient client; Uri address; var tcs = new TaskCompletionSource<string>(); DownloadStringCompletedEventHandler hander = null; handler = (_, e)=> { client.DownloadStringCompleted -= handler; if(e.Cancelled) tcs.TrySetCanceled(); else if(e.Error != null) tcs.TrySetException(e.Error); else tcs.TrySetResult(e.Result); } client.DownloadStringCompleted += handler; client.DownloadStringAsync(address); return tcs.Task;可以看到TaskCompletionSource提供了一种手动指定Task结果来构造Task的方式。
上面写了那么多,真没有信息保证全部都是正确的。最后推荐3篇文章,相信它们对理解async异步方法会有很大帮助,本文的很多知识点也是来自这几篇文章:
Understanding C# async / await (1) Compilation
Understanding C# async / await (2) Awaitable-Awaiter Pattern
Understanding C# async / await (3) Runtime Context
WinRT 异步编程 C#
WinRT是完全不同于.NET的一种框架,目地就是把Windows的底层包装成API让各种语言都可以简单的调用。WinRT中对异步的实现也和.NET完全不同,这一小节先看一下WinRT中异步机制的实现方法,再来看一下怎样使用C#和.NET与WinRT中的异步API进行交互。
前文提到async异步编程中两个比较重要的对象是awaitable和awaiter。在WinRT中充当awaitable的是IAsyncInfo接口的对象,具体使用中有如下4个实现IAsyncInfo接口的类型:
IAsyncAction
IAsyncActionWithProgress<TProgress>
IAsyncOperation<TResult>
IAsyncOperationWithProgress<TResult, TProgress>
由泛型参数可以看出Action和Operation结尾的两个类型不同之处在于IAsyncAction的GetResults方法返回void,而IAsyncOperation<TResult>的GetResults方法返回一个对象。WithProgress结尾的类型在类似类型的基础上增加了进度报告功能(它们内部定义了Progress事件用来执行进度变更时的处理函数)。
Task和IAsyncInfo分别是对.NET和WinRT中异步任务的包装。它们的原理相同但具体实现有所不同。IAsyncInfo表示的任务的状态(可以通过Status属性查询)有如下几种(和Task对照,整理自MSDN):
Task状态
(TaskStatus类型)
IAsyncInfo状态
(AsyncStatus类型)
RanToCompletion
Completed
Faulted
Error
Canceled
Canceled
所有其他值和已请求的取消
Canceled
所有其他值和未请求的取消
Started
另外获取异常的方式也不一样,通过Task中的Exception属性可以直接得到.NET异常,而IAsynInfo中错误是通过ErrorCode属性公开的一个HResult类型的错误码。当时用下文价绍的方法将IAsynInfo转为Task时,HResult会被映射为.NET Exception。
之前我们说这些IAsyncXXX类型是awaitable的,但为什么这些类型中没有GetAwaiter方法呢。真相是GetAwaiter被作为定义在.NET的程序集System.Runtime.WindowsRuntime.dll中的扩展方法,因为基本上来说async/awati还是C#使用的关键字,而C#主要以.NET为主。
这些扩展方法声明形如(有多个重载,下面是其中2个):
public static TaskAwaiter GetAwaiter<TResult>(this IAsyncAction source); public static TaskAwaiter<TResult> GetAwaiter<TResult, TProgress>(this IAsyncOperationWithProgress<TResult, TProgress> source);我们又见到了熟悉的TaskAwaiter。这个方法的实现其实也很简单(以第一个重载为例):
public static TaskAwaiter GetAwaiter(this IAsyncAction source) { return WindowsRuntimeSystemExtensions.AsTask(source).GetAwaiter(); }可以看到就是通过task.GetAwaiter得到的TaskAwaiter对象。
这一系列扩展方法的背后又有一个更重要的扩展方法 - AsTask()。
AsTask方法有更多的重载,其实现原理和前文介绍将EAP包装为async异步模式的代码差不多,都是通过TaskCompletionSource来手工构造Task。下面展示的是一个最复杂的重载的实现:
public static Task<TResult> AsTask<TResult, TProgress>( this IAsyncOperationWithProgress<TResult, TProgress> source, CancellationToken cancellationToken, IProgress<TProgress> progress) { if (source == null) throw new ArgumentNullException("source"); TaskToAsyncOperationWithProgressAdapter<TResult, TProgress> withProgressAdapter = source as TaskToAsyncOperationWithProgressAdapter<TResult, TProgress>; if (withProgressAdapter != null && !withProgressAdapter.CompletedSynchronously) { Task<TResult> task = withProgressAdapter.Task as Task<TResult>; if (!task.IsCompleted) { if (cancellationToken.CanBeCanceled && withProgressAdapter.CancelTokenSource != null) WindowsRuntimeSystemExtensions.ConcatenateCancelTokens(cancellationToken, withProgressAdapter.CancelTokenSource, (Task) task); if (progress != null) WindowsRuntimeSystemExtensions.ConcatenateProgress<TResult, TProgress>(source, progress); } return task; } switch (source.Status) { case AsyncStatus.Completed: return Task.FromResult<TResult>(source.GetResults()); case AsyncStatus.Canceled: return Task.FromCancellation<TResult>(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true)); case AsyncStatus.Error: return Task.FromException<TResult>(RestrictedErrorInfoHelper.AttachRestrictedErrorInfo(source.get_ErrorCode())); default: if (progress != null) WindowsRuntimeSystemExtensions.ConcatenateProgress<TResult, TProgress>(source, progress); AsyncInfoToTaskBridge<TResult, TProgress> infoToTaskBridge = new AsyncInfoToTaskBridge<TResult, TProgress>(); try { source.Completed = new AsyncOperationWithProgressCompletedHandler<TResult, TProgress>(infoToTaskBridge.CompleteFromAsyncOperationWithProgress); infoToTaskBridge.RegisterForCancellation((IAsyncInfo) source, cancellationToken); } catch { if (Task.s_asyncDebuggingEnabled) Task.RemoveFromActiveTasks(infoToTaskBridge.Task.Id); throw; } return infoToTaskBridge.Task; } }