《C#并发编程经典实例》学习笔记—2.6 任务完成时的处理
问题
正在 await 一批任务,希望在每个任务完成时对它做一些处理。另外,希望在任务一完成就立即进行处理,而不需要等待其他任务。
问题的重点在于希望任务完成之后立即进行处理,而不去等待其他任务。
这里还沿用文中的例子。
等待几秒钟之后返回等待的秒数,之后立即打印任务等待的秒数。
等待的函数如下
static async Task<int> DelayAndReturnAsync(int val)
{
      await Task.Delay(TimeSpan.FromSeconds(val));
      return val;
}
以下方法执行之后的打印结果是“2”, “3”, “1”。想得到结果“1”, “2”, “3”应该如何实现。
static async Task ProcessTasksAsync()
{
    // 创建任务队列。
    Task<int> taskA = DelayAndReturnAsync(2);
    Task<int> taskB = DelayAndReturnAsync(3);
    Task<int> taskC = DelayAndReturnAsync(1);
    var tasks = new[] { taskA, taskB, taskC };
    // 按顺序 await 每个任务。
    foreach (var task in tasks)
    {
        var result = await task;
        Trace.WriteLine(result);
    }
}
文中给了两种解决方案。一种是抽出更高级的async方法,一种是借助作者的nuget拓展。作者还推荐了另外两个博客文章。
Processing tasks as they complete
ORDERING BY COMPLETION, AHEAD OF TIME
这两篇文章介绍了更多处理方法。
抽象方法,并发执行
static async Task AwaitAndProcessAsync(Task<int> task)
{
    var result = await task;
    Trace.WriteLine(result);
}
将执行和处理抽象出来,借助Task.WhenAll和LINQ并发执行。
var processingTasks = (from t in tasks
select AwaitAndProcessAsync(t)).ToArray();
// 等待全部处理过程的完成。
await Task.WhenAll(processingTasks);
或者
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Trace.WriteLine(result);
}).ToArray();
// 等待全部处理过程的完成。
await Task.WhenAll(processingTasks);
借助nuget拓展:Nito.AsyncEx
推荐预发布版本:https://www.nuget.org/packages/Nito.AsyncEx/5.0.0-pre-06
需要添加引用using Nito.AsyncEx;
static async Task UseOrderByCompletionAsync()
{
      // 创建任务队列。
      Task<int> taskA = DelayAndReturnAsync(2);
      Task<int> taskB = DelayAndReturnAsync(3);
      Task<int> taskC = DelayAndReturnAsync(1);
      var tasks = new[] { taskA, taskB, taskC };
      // 等待每一个任务完成。
      foreach (var task in tasks.OrderByCompletion())
      {
           var result = await task;
           Trace.WriteLine(result);
      }
}
串行执行
使用ConcurrentExclusiveSchedulerPair,使任务串行执行,结果是“2”, “3”, “1”。
var scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler;
foreach (var t in tasks)
{
    await t.ContinueWith(completed =>
    {
             switch (completed.Status)
             {
                   case TaskStatus.RanToCompletion:
                   Trace.WriteLine(completed.Result);
                   //Process(completed.Result);
                   break;
                   case TaskStatus.Faulted:
                   //Handle(completed.Exception.InnerException);
                   break;
               }
     }, scheduler);
}
上篇文章中提到了使用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的解决方案。
/// <summary>
/// 返回一系列任务,这些任务的输入类型相同和返回结果类型一致
/// 返回的任务将以完成顺序返回
/// </summary>
private static IEnumerable<Task<T>> OrderByCompletion<T>(IEnumerable<Task<T>> inputTasks)
{
    // 复制输入,以下的处理将不需要考虑是否会对输入有影响
    var inputTaskList = inputTasks.ToList();
    var completionSourceList = new List<TaskCompletionSource<T>>(inputTaskList.Count);
    for (int i = 0; i < inputTaskList.Count; i++)
    {
        completionSourceList.Add(new TaskCompletionSource<T>());
    }
    // 索引
    // 索引最好是从0开始,但是 Interlocked.Increment 返回的是递增之后的值,所以这里应该赋值-1
    int prevIndex = -1;
    // 可以不用再循环之外处理Action,这样会让代码更清晰。现在有C#7.0的新特性本地方法可以使用
     /* //本地方法
     void continuation(Task<T> completedTask)
     {
          int index = Interlocked.Increment(ref prevIndex);
          var source = completionSourceList[index];
          PropagateResult(completedTask, source);
     }*/ 
    Action<Task<T>> continuation = completedTask =>
    {
        int index = Interlocked.Increment(ref prevIndex);
        var source = completionSourceList[index];
        PropagateResult(completedTask, source);
    };
    foreach (var inputTask in inputTaskList)
    {
        inputTask.ContinueWith(continuation,
                               CancellationToken.None,
                               TaskContinuationOptions.ExecuteSynchronously,
                               TaskScheduler.Default);
    }
    return completionSourceList.Select(source => source.Task);
}
/// <summary>
/// 对 TaskCompletionSource 进行处理
/// </summary>
private static void PropagateResult<T>(Task<T> completedTask,
    TaskCompletionSource<T> completionSource)
{
    switch (completedTask.Status)
    {
        case TaskStatus.Canceled:
            completionSource.TrySetCanceled();
            break;
        case TaskStatus.Faulted:
            completionSource.TrySetException(completedTask.Exception.InnerExceptions);
            break;
        case TaskStatus.RanToCompletion:
            completionSource.TrySetResult(completedTask.Result);
            break;
        default:
            throw new ArgumentException("Task was not completed");
    }
}
《C#并发编程经典实例》学习笔记—2.6 任务完成时的处理的更多相关文章
- 《C#并发编程经典实例》笔记
		1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ... 
- 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续
		避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ... 
- 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理
		问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ... 
- 《C#并发编程经典实例》学习笔记—2.3 报告任务
		问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ... 
- 《C# 并发编程 · 经典实例》读书笔记
		前言 最近在看<C# 并发编程 · 经典实例>这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例 ... 
- [书籍]用UWP复习《C#并发编程经典实例》
		1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ... 
- 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解
		误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ... 
- 《C#并发编程经典实例》学习笔记-第一章并发编程概述
		并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ... 
- 并发编程概述--C#并发编程经典实例
		优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ... 
- 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象
		Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ... 
随机推荐
- MySQL(七)DQL之分组查询
			一.语法 select 分组函数,分组后的字段from 表[where 筛选条件]group by 分组的字段[having 分组后的筛选][order by 排序列表] 二.特点 分组前筛选:whe ... 
- typeof和instansof的区别
			typeof typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类型.(typeof 运算符返回一个用来表示表达式的数据类型的字符串. ... 
- Helm学习笔记
			Helm学习笔记 Helm 是 Kubernetes 生态系统中的一个软件包管理工具.本文将介绍 Helm 中的相关概念和基本工作原理,并通过一个具体的示例学习如何使用 Helm 打包.分发.安装.升 ... 
- Docker 服务
			1. Docker服务 作为一名后端攻城狮,对“服务”这个概念一定不陌生.比如,我们做一个会员系统,它可能会需要数据库.缓存.消息队列,这些都是中间件服务,除此以外可能还需要依赖其它的Dubbo服务 ... 
- Netty源码—六、tiny、small内存分配
			tiny内存分配 tiny内存分配流程: 如果申请的是tiny类型,会先从tiny缓存中尝试分配,如果缓存分配成功则返回 否则从tinySubpagePools中尝试分配 如果上面没有分配成功则使用a ... 
- 《深入理解Java虚拟机》读书笔记(第三章)
			垃圾收集器与内存分配策略(第三章) 前言,众所周知,Java是由c++进化而来,c++在内存需自己申请,自己释放,于是就有了Java的动态内存分配.书的第三章开篇,有这样一句话描述的很妙——Java与 ... 
- html&css学习二
			表格&b标签 表格 表格标签主要包含三个标签 <table> <tr> <td>单元格内的文字</td> ... </tr> ... ... 
- css对齐方案总结
			css对齐方案总结 垂直居中 通用布局方式(内敛元素和块状元素都适用) 利用flex:核心代码: 12345 .container{ display:flex; flex-direction:colu ... 
- openlayers4 入门开发系列之地图工具栏篇(附源码下载)
			前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ... 
- Android之greenDao使用
			文章大纲 一.greenDao简介二.greenDao实战三.项目源码下载四.参考文章 一.greenDao简介 1. 什么是greenDao GreenDAO是一个开源的Android OR ... 
