对于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. java web项目优化记录:优化考试系统

    考试系统在进行压力測试时发现,并发量高之后出现了button无反应.试题答案不能写到数据库的问题,于是针对这些核心问题,进行了优化. 数据库方面: Select语句:Select * from TEB ...

  2. Spring的AOP特性

    一.AOP简介 AOP是Aspect-Oriented Programming的缩写,即面向切面编程.利用oop思想,可以很好的处理业务流程,但是不能把系统中某些特定的重复性行为封装到模块中.例如,在 ...

  3. 【CODEFORCES】 D. CGCDSSQ

    D. CGCDSSQ time limit per test 2 seconds memory limit per test 256 megabytes input standard input ou ...

  4. android学习笔记:adb更换端口后成功启动

    搭建手机开发环境,android ADT,android SDK,然后按照PhoneGap官网的指引,拷贝文件,修改代码,运行,进度条到了某个位置后就停止不动了. 停止不动,又是停止不动.你都不知道问 ...

  5. iOS开发】之CocoaAsyncSocket使用

    本文介绍了CocoaAsyncSocket库中GCDAsyncSocket类的使用.粘包处理以及时间延迟测试. 一.CocoaAsyncSocket介绍 CocoaAsyncSocket中主要包含两个 ...

  6. B1297 [SCOI2009]迷路 矩阵

    这个题我觉得很有必要写一篇博客.首先,我们需要知道,假如一个邻接矩阵只有0/1构成,那么它自己的n次方就是走n步之后的方案数.但这个题还有2~9咋办呢.我们观察发现,这个题只有10个点,而且边权< ...

  7. App上架流程 & 上架被拒10大原因

    上架前预热 先登陆自己的开发者账号(自己提前注册好 iOS 开发者账号,这里假设你已经拥有了一个 iOS 开发者账号),进入这个页面:https://developer.apple.com/accou ...

  8. 理解JavaScript中的闭包

    (这篇文章后面关于onclick事件的解释是错误的,请不要被误导了2016.6.16) 闭包这个概念给JavaScript初学者心中留下了巨大的阴影,网络上关于闭包的文章不可谓不多,但是能让初学者看懂 ...

  9. distpicker三级联动,动态改变省市信息

    一.引入3个js文件 <script type="text/javascript" src="js/distpicker.data.js">< ...

  10. 题解报告:hdu 1213 How Many Tables

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213 Problem Description Today is Ignatius' birthday. ...