基于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类型。html

好比,思考下面的“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,本文档后面的会更深刻地讨论。async

表现

初始化异步操做

在返回结果的任务以前,基于TAP异步方法容许同步地处理少许的工做。这项工做应保持在所需的最低数量,执行如验证参数和启动异步操做的操做。极可能从用户界面线程将调用异步方法,所以全部长时间运行的异步方法的同步前期部分工做可能会损害响应能力。颇有可能同时将启动多个异步方法,所以全部长时间运行的异步方法的同步前期部分工做可能会推迟启动其余异步操做,从而减小并发的好处。异步编程

在某些状况下,完成操做所需的工做量小于异步启动操做须要的工做量(例如,从流中读取数据,这个读取操做能够被已经缓冲在内存中的数据所知足)。在这种状况下,操做可能同步完成,返回一个已经完成的任务。函数

异常

一个异步方法只应该直接捕获一个MethodNameAsync 调用时抛出的异常以响应用法错误。对于其余全部的错误,在异步方法执行期间发生的异常应该分配给返回的任务。这种状况是在Task返回以前,异步方法同步完成下发生的。通常地,一个Task至多包含一个异常。然而,对于一个Task表示多个操做(如,Task.WhenAll)的状况,单个Task也会关联多个异常。spa

【*每一个.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同样。

返回该系列目录《基于Task的异步模式--全面介绍》

相关文章
相关标签/搜索