15.6.2【Task使用】 组合异步操作
对于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使用】 组合异步操作的更多相关文章
- 15.3 Task 语法和语义
15.3.1 声明异步方法和返回类型 async static void GetStringAsync() { using (var client = new HttpClient()) { Task ...
- 实践基于Task的异步模式
Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...
- 转载 .Net多线程编程—任务Task https://www.cnblogs.com/hdwgxz/p/6258014.html
.Net多线程编程—任务Task 1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 ob ...
- .Net多线程编程—任务Task
1 System.Threading.Tasks.Task简介 一个Task表示一个异步操作,Task的创建和执行是独立的. 只读属性: 返回值 名称 说明 object AsyncState 表示在 ...
- 使用任务Task 简化异步编程
使用任务简化异步编程 Igor Ostrovsky 下载代码示例 异步编程是实现与程序其余部分并发运行的较大开销操作的一组技术. 常出现异步编程的一个领域是有图形化 UI 的程序环境:当开销较大的操作 ...
- 利用反射快速给Model实体赋值 使用 Task 简化异步编程 Guid ToString 格式知多少?(GUID 格式) Parallel Programming-实现并行操作的流水线(生产者、消费者) c# 无损高质量压缩图片代码 8种主要排序算法的C#实现 (一) 8种主要排序算法的C#实现 (二)
试想这样一个业务需求:有一张合同表,由于合同涉及内容比较多所以此表比较庞大,大概有120多个字段.现在合同每一次变更时都需要对合同原始信息进行归档一次,版本号依次递增.那么我们就要新建一张合同历史表, ...
- Spring task定时任务
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...
- 5天玩转C#并行和多线程编程 —— 第三天 认识和使用Task
5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...
- C#并行编程-Task
菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...
随机推荐
- 打造atom成为golang开发神器
在我在Windows系统上开发的日子里.我使用IDE开发数年之久.比如Visual Basic IDE, Borland Delphi IDE, Visual C++ 和最后的Visual Studi ...
- Codeforces Round #316 (Div. 2) C. Replacement(线段树)
C. Replacement time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...
- HDOJ 4705 Y 树形DP
DP:求出3点构成链的方案数 .然后总方案数减去它 Y Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K ...
- sed 之 模式空间 & 保持空间
模式空间:容纳当前输入行的缓冲区: 保持空间:作为辅助的一个缓冲区,可以和模式空间进行交互,但是命令不能直接作用于保持空间. 由上面定义可以知道,模式空间和保持空间是两个独立的缓冲区,可以进行交互,命 ...
- sql server中数据约束相关的查询
根据表名查找数据约束 SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'CMS_EventLog'; SEL ...
- 在Docker Hub上你可以很轻松下载到大量已经容器化的应用镜像,即拉即用——daocloud国内镜像加速
Docker之所以这么吸引人,除了它的新颖的技术外,围绕官方Registry(Docker Hub)的生态圈也是相当吸引人眼球的地方. 在Docker Hub上你可以很轻松下载到大量已经容器化的应用镜 ...
- Jar包中文乱码问题
项目上遇用winrar修改替换jar中一个中文文件名后出现jar包解压读取错误问题,被这个问题纠缠了两次,都是现场比较情急的情况,于是就研究一下彻底弄清楚这个问题.中间也网上搜过一些内容,但实际测试不 ...
- geronimo
时间限制 1s 空间限制 512MB 3.1 题目描述 "Geronimo∼" 时间还很多,让我们慢慢来. 不如听首开心的歌再看题?-- 算了,直接看题吧. 给定一个整数 n,以及 ...
- bzoj3663
几何+lis 很巧妙.直接做很困难,那么我们转化一下,把每个点能看见的圆弧画出来.只有这些圆弧相交时才满足条件. 那么也就是找出圆上尽量多两两相交的区间. 所以我们先按左端点极角排序,然后固定一个必须 ...
- Linux系统下vim常用快捷键及功能
1. 什么是vim Vim是一个类似于Vi的著名的功能强大.高度可定制的文本编辑器,在vi的基础上改进和增加了很多特性. vim编辑器是Linux系统下标准的编辑器,作用相当于windows系统中的记 ...