问题

正在 await 一批任务,希望在每个任务完成时对它做一些处理。另外,希望在任务一完成就立即进行处理,而不需要等待其他任务。

问题的重点在于希望任务完成之后立即进行处理,而不去等待其他任务。

这里还沿用文中的例子。

等待几秒钟之后返回等待的秒数,之后立即打印任务等待的秒数。

等待的函数如下

  1. static async Task<int> DelayAndReturnAsync(int val)
  2. {
  3. await Task.Delay(TimeSpan.FromSeconds(val));
  4. return val;
  5. }

以下方法执行之后的打印结果是“2”, “3”, “1”。想得到结果“1”, “2”, “3”应该如何实现。

  1. static async Task ProcessTasksAsync()
  2. {
  3. // 创建任务队列。
  4. Task<int> taskA = DelayAndReturnAsync(2);
  5. Task<int> taskB = DelayAndReturnAsync(3);
  6. Task<int> taskC = DelayAndReturnAsync(1);
  7. var tasks = new[] { taskA, taskB, taskC };
  8. // 按顺序 await 每个任务。
  9. foreach (var task in tasks)
  10. {
  11. var result = await task;
  12. Trace.WriteLine(result);
  13. }
  14. }

文中给了两种解决方案。一种是抽出更高级的async方法,一种是借助作者的nuget拓展。作者还推荐了另外两个博客文章。

Processing tasks as they complete

ORDERING BY COMPLETION, AHEAD OF TIME

这两篇文章介绍了更多处理方法。

抽象方法,并发执行

  1. static async Task AwaitAndProcessAsync(Task<int> task)
  2. {
  3. var result = await task;
  4. Trace.WriteLine(result);
  5. }

将执行和处理抽象出来,借助Task.WhenAll和LINQ并发执行。

  1. var processingTasks = (from t in tasks
  2. select AwaitAndProcessAsync(t)).ToArray();
  3. // 等待全部处理过程的完成。
  4. await Task.WhenAll(processingTasks);

或者

  1. var processingTasks = tasks.Select(async t =>
  2. {
  3. var result = await t;
  4. Trace.WriteLine(result);
  5. }).ToArray();
  6. // 等待全部处理过程的完成。
  7. await Task.WhenAll(processingTasks);

借助nuget拓展:Nito.AsyncEx

推荐预发布版本:https://www.nuget.org/packages/Nito.AsyncEx/5.0.0-pre-06

需要添加引用using Nito.AsyncEx;

  1. static async Task UseOrderByCompletionAsync()
  2. {
  3. // 创建任务队列。
  4. Task<int> taskA = DelayAndReturnAsync(2);
  5. Task<int> taskB = DelayAndReturnAsync(3);
  6. Task<int> taskC = DelayAndReturnAsync(1);
  7. var tasks = new[] { taskA, taskB, taskC };
  8. // 等待每一个任务完成。
  9. foreach (var task in tasks.OrderByCompletion())
  10. {
  11. var result = await task;
  12. Trace.WriteLine(result);
  13. }
  14. }

串行执行

使用ConcurrentExclusiveSchedulerPair,使任务串行执行,结果是“2”, “3”, “1”。

  1. var scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler;
  2. foreach (var t in tasks)
  3. {
  4. await t.ContinueWith(completed =>
  5. {
  6. switch (completed.Status)
  7. {
  8. case TaskStatus.RanToCompletion:
  9. Trace.WriteLine(completed.Result);
  10. //Process(completed.Result);
  11. break;
  12. case TaskStatus.Faulted:
  13. //Handle(completed.Exception.InnerException);
  14. break;
  15. }
  16. }, scheduler);
  17. }

上篇文章中提到了使用Task.WhenAny处理已完成的任务:https://www.cnblogs.com/AlienXu/p/10609253.html#idx_2

文中的例子从算法层面是不推荐使用的,作者推荐了他自己的拓展Nito.AsyncEx,源码地址:https://github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Tasks/TaskExtensions.cs。

另外两种实现的实现方法差不多,都是借助TaskCompletionSource<T>和Interlocked.Incrementa处理Task。

这里只列出ORDERING BY COMPLETION, AHEAD OF TIME的解决方案。

  1. /// <summary>
  2. /// 返回一系列任务,这些任务的输入类型相同和返回结果类型一致
  3. /// 返回的任务将以完成顺序返回
  4. /// </summary>
  5. private static IEnumerable<Task<T>> OrderByCompletion<T>(IEnumerable<Task<T>> inputTasks)
  6. {
  7. // 复制输入,以下的处理将不需要考虑是否会对输入有影响
  8. var inputTaskList = inputTasks.ToList();
  9. var completionSourceList = new List<TaskCompletionSource<T>>(inputTaskList.Count);
  10. for (int i = 0; i < inputTaskList.Count; i++)
  11. {
  12. completionSourceList.Add(new TaskCompletionSource<T>());
  13. }
  14. // 索引
  15. // 索引最好是从0开始,但是 Interlocked.Increment 返回的是递增之后的值,所以这里应该赋值-1
  16. int prevIndex = -1;
  17. // 可以不用再循环之外处理Action,这样会让代码更清晰。现在有C#7.0的新特性本地方法可以使用
  18. /* //本地方法
  19. void continuation(Task<T> completedTask)
  20. {
  21. int index = Interlocked.Increment(ref prevIndex);
  22. var source = completionSourceList[index];
  23. PropagateResult(completedTask, source);
  24. }*/
  25. Action<Task<T>> continuation = completedTask =>
  26. {
  27. int index = Interlocked.Increment(ref prevIndex);
  28. var source = completionSourceList[index];
  29. PropagateResult(completedTask, source);
  30. };
  31. foreach (var inputTask in inputTaskList)
  32. {
  33. inputTask.ContinueWith(continuation,
  34. CancellationToken.None,
  35. TaskContinuationOptions.ExecuteSynchronously,
  36. TaskScheduler.Default);
  37. }
  38. return completionSourceList.Select(source => source.Task);
  39. }
  40. /// <summary>
  41. /// 对 TaskCompletionSource 进行处理
  42. /// </summary>
  43. private static void PropagateResult<T>(Task<T> completedTask,
  44. TaskCompletionSource<T> completionSource)
  45. {
  46. switch (completedTask.Status)
  47. {
  48. case TaskStatus.Canceled:
  49. completionSource.TrySetCanceled();
  50. break;
  51. case TaskStatus.Faulted:
  52. completionSource.TrySetException(completedTask.Exception.InnerExceptions);
  53. break;
  54. case TaskStatus.RanToCompletion:
  55. completionSource.TrySetResult(completedTask.Result);
  56. break;
  57. default:
  58. throw new ArgumentException("Task was not completed");
  59. }
  60. }

《C#并发编程经典实例》学习笔记—2.6 任务完成时的处理的更多相关文章

  1. 《C#并发编程经典实例》笔记

    1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...

  2. 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续

    避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...

  3. 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理

    问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...

  4. 《C#并发编程经典实例》学习笔记—2.3 报告任务

    问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...

  5. 《C# 并发编程 · 经典实例》读书笔记

    前言 最近在看<C# 并发编程 · 经典实例>这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例 ...

  6. [书籍]用UWP复习《C#并发编程经典实例》

    1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...

  7. 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解

    误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...

  8. 《C#并发编程经典实例》学习笔记-第一章并发编程概述

    并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ...

  9. 并发编程概述--C#并发编程经典实例

    优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...

  10. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

随机推荐

  1. 为什么说 Java 程序员到了必须掌握 Spring Boot 的时候?

    Spring Boot 2.0 的推出又激起了一阵学习 Spring Boot 热,就单从我个人的博客的访问量大幅增加就可以感受到大家对学习 Spring Boot 的热情,那么在这么多人热衷于学习 ...

  2. 我是庖丁,<肢解IOT平台>之物模型

    前言 物模型是对设备在云端的功能描述,包括设备的属性,数据,服务和事件. 物联网平台通过定义一种物的描述语言来描述物模型,称之为 TSL(即 Thing Specification Language) ...

  3. TiDB之mac上搭建及调试技巧

    此文目的 由于本人最近已经成为TiDB的粉丝,所以就开始各种研究TiDB的源码,研究源码这个事情,首先就需要在自己电脑上不断的调试及修改.TiDB本身的代码是非常容易编译和调试的,但是要把PD.TiK ...

  4. Java进阶篇之十五 ----- JDK1.8的Lambda、Stream和日期的使用详解(很详细)

    前言 本篇主要讲述是Java中JDK1.8的一些新语法特性使用,主要是Lambda.Stream和LocalDate日期的一些使用讲解. Lambda Lambda介绍 Lambda 表达式(lamb ...

  5. Java安全(权限)框架 - Shiro 功能讲解 架构分析

    Java安全(权限)框架 - Shiro 功能讲解 架构分析 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 简述Shiro Shiro出自公司Apache(阿帕奇),是java的一 ...

  6. Windows中使用Mysql-Proxy实现Mysql读写分离

    1.简介 读写分离:当业务量上来时,往往一台单机的mysql数据库不能满足性能需求,这时候就需要配置主从库读写分离来解决性能瓶颈.简单的来说,就是原先一台数据库既读又写,现在改成一台写和1台以上读. ...

  7. 实现AutoMapper(1.0版本)

    最近有个需求就是实体之间自动转换,网上肯定有很多现成的实现,不过还是自己写了一个,就当对java高级特性的一个熟悉的过程.这中间包含了泛型,反射,lamada表达式.对于想了解java高级特性的人来说 ...

  8. 程序员如何让自己 Be Cloud Native - 配置篇

    前言 这是<程序员如何让自己 Be Cloud Native>系列文章的第二篇,从第一篇的反馈来看,有些同学反馈十二要素太形式主义,不建议盲目跟从.作者认为任何理论和技术都需要有自己的观点 ...

  9. 补习系列(16)-springboot mongodb 数据库应用技巧

    目录 一.关于 MongoDB 二.Spring-Data-Mongo 三.整合 MongoDB CRUD A. 引入框架 B. 数据库配置 C. 数据模型 D. 数据操作 E. 自定义操作 四.高级 ...

  10. OO第二单元作业小结

    前言 转眼已是第九周,第二单元的电梯系列作业已经结束,终于体验了一番多线程电梯之旅. 第一次作业是单电梯的傻瓜调度,虽然是第一次写多线程,但在课程PPT的指引下,写起来还是非常容易:第二次作业是单电梯 ...