对于C# 5异步特性,我最喜欢的一点是它可以自然而然地组合在一起。这表现为两种不同的 方式。最明显的是,异步方法返回任务,并通常会调用其他返回任务的方法。这些方法可以是直 接的异步操作(如链的最底部),也可以是更多的异步方法。所有的包装和拆包都需要将结果转 换为任务,反向操作则由编译器完成。

  另一种组合形式是,创建与操作无关的构建块来管理任务的处理。这些构建块无须知道任务 在做什么,而只是单纯待在 Task<T> 的抽象级别。这有点像LINQ操作符,只是面向的是任务而 不是序列。框架中内置了一些构建块,但也可以自行创建。

1. 在单个调用中收集结果

  例如,尝试获取若干URL。15.3.6节中一次性获取了所有URL,并在完成任务后立即停止获 取。假设这次要启动多个并行请求,然后每得到一个URL就记录下结果。记住,异步方法返回的 是已经运行的任务,因此可非常轻松地为每个URL启动一个任务:

             string[] urls = new string[] { "http://stackoverflow.com", "http://www.google.com", "http://csharpindepth.com" };
var tasks = urls.Select(async url =>
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}).ToList();

  注意,需调用 ToList() 来具体化LINQ查询。这保证了每个任务将只启动一次。否则每次迭 代 tasks 时,将会再次获取字符串。(如不释放 HttpClient ,代码会更加简单,但即便如此,代 码也不是很难看。)

  TPL提供了一个 Task.WhenAll 方法,从而将各有一个结果的多个任务组合成一个包含多个 结果的任务。常用的方法重载签名如下所示:

        public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);

  这个声明看上去非常糟糕,但在真正使用时,会发现其方法目的非常单纯。你将得到一个 List<Task<string>> ,因此可以写为:

            string[] results = await Task.WhenAll(tasks);

  所有任务均已结束,并将结果收集到一个数组中后,等待方可终止。本章前面讲过,如果多 个任务抛出异常,则只有第一个异常会立即抛出,但可总是迭代这些任务,以找到具体失败的任 务及其失败原因,或使用代码清单15-2中所示的 WithAggregatedException 扩展方法。

  如果只关注第一个返回的请求,则可使用 Task.WhenAny 方法。该方法不会等待第一个成功 完成的任务,而只会等待第一个到达终点状态的任务。

  本例中,你可能想要点特别的做法。在任务完成后报告全部结果可能会更有用些。

2. 在全部完成时收集结果

  Task.WhenAll 是.NET内置的转换构建块(transformational building block),接下来将介绍如何以类似的方式构建自己的方法。TAP文档中含有类似的示例代码,从而创建了 Interleaved 方法,这里将介绍另一版本。

  代码清单15-12旨在传递一个输入任务的序列,并返回一个输出任务的序列。两个序列中任 务的结果是相同的,但存在一个重要差异,即输出任务的完成顺序与输入完全一样,因此可以一 次 await 一个任务,并可立即得到任务结果。这听上去有些神奇,对我来说也是如此,因此我们 来看看代码,研究一下它的工作原理。

         public static IEnumerable<Task<T>> InCompletionOrder<T>(this IEnumerable<Task<T>> source)
{
var inputs = source.ToList();
var boxes = inputs.Select(x => new TaskCompletionSource<T>()).ToList(); int currentIndex = -;
foreach (var task in inputs)
{
task.ContinueWith(completed =>
{
var nextBox = boxes[Interlocked.Increment(ref currentIndex)];
PropagateResult(completed, nextBox);
}, TaskContinuationOptions.ExecuteSynchronously);
}
return boxes.Select(box => box.Task);
}

  代码清单15-12依赖TPL中一个非常重要的类型,即 TaskCompletionSource<T> 。该类型 可用于创建一个尚未含有结果的 Task ,并在之后提供结果(或异常)。它和 AsyncTaskMethod Builder<T> 都建立在相同的基础结构之上。后者为异步方法提供返回的 Task ,并在方法体完成 时,将带结果的任务向外传播。

  为什么会用这么奇怪的变量名( boxes )呢?我常常把任务想象成纸箱,这些纸箱承诺 (promise)在某个时刻,其内部会含有值或错误。 TaskCompletionSource<T> 就像是背面有洞 的箱子,你可以把它给别人,然后再偷偷地把值从洞口塞进去 ① 。这正是 PropagateResult 方法 的作用,不过它没那么有意思,所以此处不予列出,基本上它会将已完成的 Task<T> 的结果传播 到 TaskCompletionSource<T> 中。如果原始任务正常完成,则将返回值复制到 Task CompletionSource<T> 中。如果原始任务产生了错误,则可将异常复制到 TaskCompletion Source<T> 中。取消原始任务后, TaskCompletionSource<T> 也会随之被取消。

  真正聪明的部分是(我对此说法不承担任何责任——有人发邮件建议我加入这一免责声明), 在该方法运行时,它并不知道哪个 TaskCompletionSource<T> 会对应哪个输入任务,而只是将 相同的后续操作附加到各任务上,然后由后续操作来寻找下一个 TaskCompletionSource<T> (通过对一个计数器进行原子地累加)并传播结果。也就是说,它会按照原始任务的输出顺序对箱子进行填充。

  图15-5展示了三个输入任务,以及相应的由方法返回的输出任务。即使输入任务的顺序与方 法返回的顺序不同,输出任务的顺序也会与之相同。 有了这个绝妙的扩展方法后,即可编写代码清单15-13,从而得到一组URL,并行地对每个 URL发起请求,并在请求完成时写下各页面的长度,然后返回总长度。

         static void Main()
{
var task = ShowPageLengthsAsync("http://stackoverflow.com", "http://www.google.com", "http://csharpindepth.com");
Console.WriteLine("Total length: {0}", task.Result);
}
static async Task<int> ShowPageLengthsAsync(params string[] urls)
{
var tasks = urls.Select(async url =>
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}).ToList(); int total = ;
foreach (var task in tasks.InCompletionOrder())
{
string page = await task;
Console.WriteLine("Got page length {0}", page.Length);
total += page.Length;
}
return total;
}

  代码清单15-13存在两个小问题。
  1.一个任务失败,则整个异步操作都将失败,并且不会保留结果。这也许没问题,但也可能希望能够将每次失败记录下来。(与.NET 4不同,不处理任务异常,则默认不会让进程当掉,但至少应考虑对其他任务产生的影响。)
    2.失去对页面转向具体URL的跟踪。
这两个问题都可以通过少量代码轻松解决,也可以进一步提取成可复用的构建块。举这些例子并不是为了满足个别需求,而是为了让你接受组合带来的各种可能性。TAP白皮书中并不是只有 Interleaved 这一个例子,它还包括很多概念,并附带一些有助于理解的示例。

15.6.2【Task使用】 组合异步操作的更多相关文章

  1. 15.3 Task 语法和语义

    15.3.1 声明异步方法和返回类型 async static void GetStringAsync() { using (var client = new HttpClient()) { Task ...

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

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

  3. 转载 .Net多线程编程—任务Task https://www.cnblogs.com/hdwgxz/p/6258014.html

    .Net多线程编程—任务Task   1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 ob ...

  4. .Net多线程编程—任务Task

    1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 object AsyncState 表示在 ...

  5. 使用任务Task 简化异步编程

    使用任务简化异步编程 Igor Ostrovsky 下载代码示例 异步编程是实现与程序其余部分并发运行的较大开销操作的一组技术. 常出现异步编程的一个领域是有图形化 UI 的程序环境:当开销较大的操作 ...

  6. 利用反射快速给Model实体赋值 使用 Task 简化异步编程 Guid ToString 格式知多少?(GUID 格式) Parallel Programming-实现并行操作的流水线(生产者、消费者) c# 无损高质量压缩图片代码 8种主要排序算法的C#实现 (一) 8种主要排序算法的C#实现 (二)

    试想这样一个业务需求:有一张合同表,由于合同涉及内容比较多所以此表比较庞大,大概有120多个字段.现在合同每一次变更时都需要对合同原始信息进行归档一次,版本号依次递增.那么我们就要新建一张合同历史表, ...

  7. Spring task定时任务

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...

  8. 5天玩转C#并行和多线程编程 —— 第三天 认识和使用Task

    5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...

  9. C#并行编程-Task

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

随机推荐

  1. Linux下C++访问MySQL数据库

    由于想要开始了解并学习用LAMP进行web开发,所以昨晚我在Fedora上安装了MySQL,学习了MySQL的几个常用命令.想着在学习进行web开发(PHP访问数据库)之前,先用我熟悉的C++连接数据 ...

  2. Matlab得到二值图像中最大连通区域

    有时候要将二值化图像中最大的连通域保存下来.以下函数提供了一种方法: %function [img]=maxLianTongYu(I):求图像中最大的连通域 %输入:I 输入图像 %输出:img 仅包 ...

  3. 利用FFmpge进行视频压缩(从图像到H264视频流)

    对于FFmpeg相信做视频或图像处理这一块的都不会陌生,在网上也能找到非常多相关的代码.但因为版本号不同等原因.往往找到的代码都是须要自行改动才干够用,为此本人希望能尽绵薄之力,将开发包和自行编写的代 ...

  4. (寻找第K小的数&amp;&amp;寻找第K小的数的和)

    这一篇博客以一些OJ上的题目为载体,讲一下寻找第K小的数的方法 方法一: 先将数据排列好,然后,然后return a[k]或者将前K个数加起来 方法二: 基于高速排序.如,一次高速排序将某一个数放到了 ...

  5. vmware里面的linux怎么和windows相互传文件

    我们常常遇到这样的问题.高版本号的vmware遇到低版本号的linux.使用起来就比較抠脚,比方低版本号的linux安装在高版本号的vmware里. 1.不能全屏显示虚拟机 2.每次切换出来.总要按一 ...

  6. [1525] Cow Xor

    问题描述 农民约翰在喂奶牛的时候被另一个问题卡住了.他的所有N(1 <= N <= 100,000)个奶牛在他面前排成一行(按序号1..N的顺序),按照它们的社会等级排序.奶牛#1有最高的 ...

  7. JSP-Runoob:JSP 日期处理

    ylbtech-JSP-Runoob:JSP 日期处理 1.返回顶部 1. JSP 日期处理 使用JSP最重要的优势之一,就是可以使用所有Java  API.本章将会详细地讲述Java中的Date类, ...

  8. 腾讯云linux服务器安装lnmp一键包

    这边域名已经实名了. 然后修改DNS服务器 然后备案吧 还是先不备案,直接云解析DNS 哦,想起来了,阿里云自己都可以生成SSL证书.重新弄一次吧,其实腾讯云也可以申请域名型免费版DV

  9. mysql中 groupby分组

    引用自http://www.cnblogs.com/mo-beifeng/archive/2012/02/07/2341886.html#2341105 --按某一字段分组取最大(小)值所在行的数据 ...

  10. codevs 2541 幂运算(迭代加深搜索)

    2541 幂运算  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond     题目描述 Description 从m开始,我们只需要6次运算就可以计算出m31 ...