实现基于Task的异步模式
返回该系列目录《基于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有几种方法。
- 在.Net 4中,启动一个新的计算受限的task的主要方法是TaskFactory.StartNew(),该方法接受一个异步执行的委托(一般来说是一个Action或者一个Func<TResult>)。如果提供了一个Action,返回的Task就代表那个委托的异步执行操作。如果提供了一个Func<TResult>,就会返回一个Task<TResult>。存在StartNew()的重载,该重载接受CancellationToken,TaskCreationOptions,和TaskScheduler,这些都对task的调度和执行提供了细粒度的控制。作用在当前调度者的工厂实例可以作为Task类的静态属性,例如Task.Factory.StartNew()。
- 在.Net 4.5中,Task类型暴露了一个静态的Run方法作为一个StartNew方法的捷径,可以很轻松地使用它来启动一个作用在线程池上的计算受限的task。从.Net 4.5开始,对于启动一个计算受限的task,这是一个更受人喜欢的机制。当行为要求更多的细粒度控制时,才直接使用StartNew。
- Task类型公开了构造函数和Start方法。如果必须要有分离自调度的构造函数,这些就是可以使用的(正如先前提到的,公开的APIs必须只返回已经启动的tasks)。
- Task类型公开了多个ContinueWith的重载。当另外一个task完成的时候,该方法会创建新的将被调度的task。该重载接受CancellationToken,TaskCreationOptions,和TaskScheduler,这些都对task的调度和执行提供了细粒度的控制。
- 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状态的结束:
- 在Task过度到TaskStatus.Running状态之前,CancellationToken为一个发出撤销请求的创建方法的参数提供(如StartNew,Run)。
- 有这样的一个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的异步模式的更多相关文章
- 实践基于Task的异步模式
Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...
- 基于Task的异步模式的定义
返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...
- 基于Task的异步模式--全面介绍
今天是国庆长假第一天,也是今天十月的开始.每到这个时候都是看海的季节-一个看"人海"的季节.反正我是不想在这样一个尴尬期出去放松自己,于是不如在家写写博客,长点本领呢.今天就来给大 ...
- Task的异步模式
Task的异步模式 返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的 ...
- 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). 其次是 ...
- Event-based Asynchronous Pattern Overview基于事件的异步模式概览
https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...
- C#基于任务的异步模式
using System; using System.Threading; using System.Threading.Tasks; using static System.Console; //异 ...
- C# 基于任务的异步模式的创建与使用的简单示例
对于窗体程序,使用基于任务的异步模式需要用到Task类,下面示例下非常简单的用法. 1.创建一个拥有异步方法的类 该类拥有一个异步方法DoSomthingAsync,根据微软建议的命名规则该方法要带A ...
- .NET 基于任务的异步模式(Task-based Asynchronous Pattern,TAP) async await
本文内容 概述 编写异步方法 异步程序中的控制流 API 异步方法 线程 异步和等待 返回类型和参数 参考资料 下载 Demo 下载 Demo TPL 与 APM 和 EAP 结合(APM 和 EAP ...
随机推荐
- Linux SVN 命令详解(zz)
Linux下常用SVN命令 2012-04-02 11:46:00 标签:服务器 目录 Linux checkout linux系统 1.将文件checkout到本地目录 svn checkout p ...
- 用Backbone.js创建一个联系人管理系统(五)
原文: Build a Contacts Manager Using Backbone.js: Part 5 这是这系列教程最后一部分了. 之前所有的增删改都在前端完成. 这部分我们要把Contact ...
- windowsphone 的IsolatedStorageSettings存储类型
IsolatedStorageSettings 存储自定义的对象时关闭程序之后是不会被保存的 只能存储基本类型
- OD使用教程5
怎样恢复数据被改过的代码 点击m进入内存分布图 双击进入文件头 将过大的值一一修改为正常值 普通程序没有动态链接表所以值改为0 保存:首先记住地址 其次 选中差不多的长度,保存 正常的程序
- [转]SQL Server字符串处理函数大全
select语句中只能使用sql函数对字段进行操作(链接sql server), select 字段1 from 表1 where 字段1.IndexOf("云")=1;这条语句不 ...
- 1996: [Hnoi2010]chorus 合唱队
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1996 Description Input Output Sample Input 4 1701 ...
- zTree+EasyUi做权限遇到的小问题
最近需要做一个zTree+EasyUi的权限管理系统,以前有过接触,在做这一块时,用到了ztree,树来加载咱们的菜单栏,后台获取登录用户信息的权限列表,转换成json对象来加载到咱们的树当中,代码如 ...
- Centos 反向代理创建资料
1. yum update 2. sh centos.sh 3. sh upgrade_nginx.sh nginx 1.7.0 4. cd /usr/local/nginx/conf/ upload ...
- 线程池ExecutorService
说到java开发,免不了跟多线程打交道.Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动.执行和关闭, ...
- 在 Mac OS 上创建并运行 ASP.NET Core 1.0 网站
var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...