实现基于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是如何通过多个异步操作被线程化的。




本文转自tkbSimplest博客园博客,原文链接:http://www.cnblogs.com/farb/p/4870421.html,如需转载请自行联系原作者

目录
相关文章
|
供应链 监控 安全
企业如何搭建自己的联盟链 | 区块链落地项目运用开发
企业如何搭建自己的联盟链 | 区块链落地项目运用开发
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的心理测评系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的心理测评系统附带文章和源代码部署视频讲解等
169 0
|
12月前
|
监控 数据可视化 小程序
项目管理中WBS元素是什么?如何应用?
WBS(工作分解结构)是项目管理中的核心工具,将项目分解为更小、更易于管理的部分。WBS元素是构成WBS的基本单元,代表项目中的特定工作内容或任务。WBS元素最早起源于20世纪50年代的美国,现已广泛应用于建筑、软件开发、市场营销等多个领域。通过WBS元素,项目经理可以明确项目范围、促进资源分配、支持进度规划、便于风险管理、优化沟通协调和控制项目成本。创建WBS元素的过程包括确定项目目标、识别主要工作领域、细分工作领域、定义WBS元素及其关系、验证和更新WBS。尽管WBS元素有许多优点,但也存在需要时间和资源、可能过于复杂及需持续更新的缺点。
项目管理中WBS元素是什么?如何应用?
|
11月前
|
搜索推荐 数据挖掘 API
探讨京东商品 API 接口:运用及收益
在数字化商业时代,京东商品 API 接口为企业和开发者提供了丰富的数据资源和应用机会。通过获取商品信息、价格、库存、用户评价等数据,API 接口助力电商平台建设、数据分析、移动应用开发和营销推广,提升商业价值、优化用户体验,并支持战略决策。遵守规范、保障数据安全是实现长期发展的关键。
260 2
|
12月前
|
存储 索引 Python
从中序与后序遍历序列构造二叉树
【10月更文挑战第13天】这段内容介绍了一种基于中序和后序遍历序列构建二叉树的方法。通过识别后序遍历中的根节点,并利用中序遍历划分左右子树,进而递归构建整棵树。文中提供了具体示例及Python代码实现,并分析了该方法的时间与空间复杂度。
318 0
|
12月前
|
Java API Maven
使用 Smart-doc 记录 Spring REST API
使用 Smart-doc 记录 Spring REST API
259 0
|
消息中间件
failed to open log file at ‘/var/log/rabbitmq/rabbit@9f987b50c687_upgrade.log‘, reason: permission d
failed to open log file at ‘/var/log/rabbitmq/rabbit@9f987b50c687_upgrade.log‘, reason: permission d
494 0
|
前端开发 JavaScript 数据管理
基于Springboot+Vue实现学生信息管理系统
基于Springboot+Vue实现学生信息管理系统
349 1
|
Windows
无法保存对hosts权限所作的更改 拒绝访问(权限,防止Windows主机文件、进程、注册表项进行操作和更改)
为Windows安装或修改配置、 无法保存对hosts权限所作的更改 拒绝访问 (权限,防止Windows主机文件、进程、注册表项进行操作和更改) 例如,下图: 1.使用Windows的“管理员权限”进行操作 或给当前的Windows用户分配此文件的写权限: 2.如果为文件的修改,可以先创建和复制文件副本其他地方,修改后覆盖 3.计算机安装的杀毒软件和管理软件,控制了
9711 0
|
Android开发 开发者 Windows
联想Tab M10 FHD PLUS (TB-X606F)安卓10版本刷TWRP及Magisk
联想Tab M10 FHD PLUS (TB-X606F)安卓10版本刷TWRP及Magisk
1843 1
联想Tab M10 FHD PLUS (TB-X606F)安卓10版本刷TWRP及Magisk