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

生成方法

编译器生成

在.NET Framework 4.5中,C#编译器实现了TAP。任何标有async关键字的方法都是异步方法,编译器会使用TAP执行必要的转换从而异步地实现方法。这样的方法应该返回Task或者Task<TResult>类型。在后者的案例中,方法体应该返回一个TResult,且编译器将确保通过返回的Task<TResult>是可利用的。相似地,方法体内未经处理的异常会被封送到输出的task,造成返回的Task以Faulted的状态结束。一个例外是如果OperationCanceledException(或派生类型)未经处理,那么返回的Task会以Canceled状态结束。

手动生成

开发者可以手动地实现TAP,就像编译器那样或者更好地控制方法的实现。编译器依赖来自System.Threading.Tasks命名空间暴露的公开表面区域(和建立在System.Threading.Tasks之上的System.Runtime.CompilerServices中支持的类型),还有对开发者直接可用的功能。当手动实现TAP方法时,开发者必须保证当异步操作完成时,完成返回的Task。

混合生成

在编译器生成的实现中混合核心逻辑的实现,对于手动实现TAP通常是很有用的。比如这种情况,为了避免方法直接调用者产生而不是通过Task暴露的异常,如:

public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
} private async Task<int> MethodAsyncInternal(string input)
{
… // code that uses await
}

参数应该在编译器生成的异步方法之外改变,这种委托有用的另一种场合是,当一个“快速通道”优化可以通过返回一个缓存的task来实现的时候。

工作负荷

计算受限和I/O受限的异步操作可以通过TAP方法实现。然而,当TAP的实现从一个库公开暴露时,应该只提供给包含I/O操作的工作负荷(它们也可以包含计算,但不应该只包含计算)。如果一个方法纯粹受计算限制,它应该只通过一个异步实现暴露,消费者然后就可以为了把该任务卸载给其他的线程的目的来选择是否把那个同步方法的调用包装成一个Task,并且/或者来实现并行。

计算限制

Task类最适合表示计算密集型操作。默认地,为了提供有效的执行操作,它利用了.Net线程池中特殊的支持,同时也对异步计算何时,何地,如何执行提供了大量的控制。

生成计算受限的tasks有几种方法。

  1. 在.Net 4中,启动一个新的计算受限的task的主要方法是TaskFactory.StartNew(),该方法接受一个异步执行的委托(一般来说是一个Action或者一个Func<TResult>)。如果提供了一个Action,返回的Task就代表那个委托的异步执行操作。如果提供了一个Func<TResult>,就会返回一个Task<TResult>。存在StartNew()的重载,该重载接受CancellationToken,TaskCreationOptions,和TaskScheduler,这些都对task的调度和执行提供了细粒度的控制。作用在当前调度者的工厂实例可以作为Task类的静态属性,例如Task.Factory.StartNew()。

  2. 在.Net 4.5中,Task类型暴露了一个静态的Run方法作为一个StartNew方法的捷径,可以很轻松地使用它来启动一个作用在线程池上的计算受限的task。从.Net 4.5开始,对于启动一个计算受限的task,这是一个更受人喜欢的机制。当行为要求更多的细粒度控制时,才直接使用StartNew。
  3. Task类型公开了构造函数和Start方法。如果必须要有分离自调度的构造函数,这些就是可以使用的(正如先前提到的,公开的APIs必须只返回已经启动的tasks)。
  4. Task类型公开了多个ContinueWith的重载。当另外一个task完成的时候,该方法会创建新的将被调度的task。该重载接受CancellationToken,TaskCreationOptions,和TaskScheduler,这些都对task的调度和执行提供了细粒度的控制。
  5. TaskFactory类提供了ContinueWhenAll 和ContinueWhenAny方法。当提供的一系列的tasks中的所有或任何一个完成时,这些方法会创建一个即将被调度的新的task。有了ContinueWith,就有了对于调度的控制和任务的执行的支持。

思考下面的渲染图片的异步方法。task体可以获得cancellation token为的是,当渲染发生的时候,如果一个撤销请求到达后,代码可能过早退出。而且,如果一个撤销请求在渲染开始之前发生,我们也可以阻止任何的渲染。

public Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
… // render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}

如果下面的条件至少一个是正确的,计算受限的tasks会以一个Canceled状态的结束:

  1. 在Task过度到TaskStatus.Running状态之前,CancellationToken为一个发出撤销请求的创建方法的参数提供(如StartNew,Run)。

  2. 有这样的一个Task,它内部有未处理的OperationCanceledException。该OperationCanceledException 包含和CancellationToken属性同名的CancellationToken传递到该Task,且该CancellationToken已经发出了撤销请求。

如果该Task体中有另外一个未经处理的异常,那么该Task就会以Faulted的状态结束,同时在该task上等待的任何尝试或者访问它的结果都将导致抛出异常。

I/O限制

使用TaskCompletionSource<TResult>类型创建的Tasks不应该直接被全部执行的线程返回。TaskCompletionSource<TResult>暴露了一个返回相关的Task<TResult>实例的Task属性。该task的生命周期通过TaskCompletionSource<TResult>实例暴露的方法控制,换句话说,这些实例包括SetResult, SetException, SetCanceled, 和它们的TrySet* 变量。

思考这样的需求,创建一个在特定的时间之后会完成的task。比如,当开发者在UI场景中想要延迟一个活动一段时间时,这可能使有用的。.NET中的System.Threading.Timer类已经提供了这种能力,在一段特定时间后异步地调用一个委托,并且我们可以使用TaskCompletionSource<TResult>把一个Task放在timer上,例如:

public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
var tcs = new TaskCompletionSource<DateTimeOffset>();
new Timer(self =>
{
((IDisposable)self).Dispose();
tcs.TrySetResult(DateTimeOffset.UtcNow);
}).Change(millisecondsTimeout, -1);
return tcs.Task;
}

在.Net 4.5中,Task.Delay()就是为了这个目的而生的。比如,这样的一个方法可以使用到另一个异步方法的内部,以实现一个异步的轮训循环:

public static async Task Poll(
Uri url,
CancellationToken cancellationToken,
IProgress<bool> progress)
{
while(true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }
progress.Report(success);
}
}

没有TaskCompletionSource<TResult>的非泛型副本。然而,Task<TResult>派生自Task,因而,泛型的TaskCompletionSource<TResult>可以用于那些 I/O受限的方法,它们都利用一个假的TResult源(Boolean是默认选择,如果开发者关心Task向下转型的Task<TResult>的消费者,那么可以使用一个私有的TResult类型)仅仅返回一个Task。比如,开发的之前的Delay方法是为了顺着产生的Task<DateTimeOffset>返回当前的时间。如果这样的 一个结果值是不必要的,那么该方法可以通过下面的代码取而代之(注意返回类型的改变和TrySetresult参数的改变):

public static Task Delay(int millisecondsTimeout)
{
var tcs = new TaskCompletionSource<bool>();
new Timer(self =>
{
((IDisposable)self).Dispose();
tcs.TrySetResult(true);
}).Change(millisecondsTimeout, -1);
return tcs.Task;
}

混合计算限制和I/O限制的任务

异步方法不是仅仅受限于计算受限或者I/O受限的操作,而是可以代表这两者的混合。实际上,通常情况是不同性质的多个异步操作被组合在一起生成更大的混合操作。比如,思考之前的RenderAsync方法,该方法基于一些输入的ImageData执行一个计算密集的操作来渲染一张图片。该ImageData可能来自于一个我们异步访问的Web服务:

public async Task<Bitmap> DownloadDataAndRenderImageAsync(
CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}

这个例子也展示了一个单独的CancellationToken是如何通过多个异步操作被线程化的。

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


实现基于Task的异步模式的更多相关文章

  1. 实践基于Task的异步模式

    Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...

  2. 基于Task的异步模式的定义

    返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...

  3. 基于Task的异步模式--全面介绍

    今天是国庆长假第一天,也是今天十月的开始.每到这个时候都是看海的季节-一个看"人海"的季节.反正我是不想在这样一个尴尬期出去放松自己,于是不如在家写写博客,长点本领呢.今天就来给大 ...

  4. Task的异步模式

    Task的异步模式 返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的 ...

  5. C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)

    学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...

  6. Event-based Asynchronous Pattern Overview基于事件的异步模式概览

    https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...

  7. C#基于任务的异步模式

    using System; using System.Threading; using System.Threading.Tasks; using static System.Console; //异 ...

  8. C# 基于任务的异步模式的创建与使用的简单示例

    对于窗体程序,使用基于任务的异步模式需要用到Task类,下面示例下非常简单的用法. 1.创建一个拥有异步方法的类 该类拥有一个异步方法DoSomthingAsync,根据微软建议的命名规则该方法要带A ...

  9. .NET 基于任务的异步模式(Task-based Asynchronous Pattern,TAP) async await

    本文内容 概述 编写异步方法 异步程序中的控制流 API 异步方法 线程 异步和等待 返回类型和参数 参考资料 下载 Demo 下载 Demo TPL 与 APM 和 EAP 结合(APM 和 EAP ...

随机推荐

  1. Hadoop2.6.0的FileInputFormat的任务切分原理分析(即如何控制FileInputFormat的map任务数量)

    前言 首先确保已经搭建好Hadoop集群环境,可以参考<Linux下Hadoop集群环境的搭建>一文的内容.我在测试mapreduce任务时,发现相比于使用Job.setNumReduce ...

  2. [WPF]Slider控件常用方法

    WPF的Slider控件继承自RangeBase类型,同继承自RangeBase的控件还有ProgressBar和ScrollBar,这类控件都是在一定数值范围内表示一个值的用途. 首先注意而Rang ...

  3. jquery easyui的treegrid的控制

    其中列的formatter很有作用,可以得到你想要的任何内容: 例: <table class="easyui-treegrid" id="tg" dat ...

  4. C语言教学--二维数组和指针的理解

    对于初学者对二维数组和指针的理解很模糊, 或者感觉很难理解, 其实我们和生活联系起来, 这一切都会变得清晰透彻. 我们用理解一维数组的思想来理解二维数组, 对于一维数组,每个箱子里存放的是具体的苹果, ...

  5. vim的跨文件复制粘贴

    1.用vim打开一个文件,例如:a.cpp 2.在普通模式下,输入:":sp"(不含引号)横向切分一个窗口,或者":vsp"纵向切分一个窗口,敲入命令后,你将看 ...

  6. 关于session和token

       最近做的项目是全平台的,需要给移动端做后台,有了许多改变,如是使用token而不是session.一开始我无法理解为什么不用session,看了很多文章以后才有一定了解.    例如在ios端, ...

  7. JavaScript-取消事件-e.preventDefault();

    取消事件:(阻止默认行为) 当事件执行过程中,遇到问题,可取消事件.不再触发 如何e.preventDefault(); <!DOCTYPE html> <html> < ...

  8. C#基于Office组件操作Excel

    1.    内容简介 实现C#与Excel文件的交互操作,实现以下功能: a)     DataTable 导出到 Excel文件 b)     Model数据实体导出到 Excel文件[List&l ...

  9. js生成验证码并检验

    <html> <head> <title>验证码</title> <style type="text/css"> #co ...

  10. iOS开发零基础--Swift教程 字符串的利用

    字符串的介绍 字符串在任何的开发中使用都是非常频繁的 OC和Swift中字符串的区别 在OC中字符串类型时NSString,在Swift中字符串类型是String OC中字符串@"" ...