问题

正在 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 任务完成时的处理的更多相关文章

  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. HTTP 和 WebSocket的区别

    有关http和WebSocket 的区别网上有很多的质料. 个人在此仅仅是记录以下自己的学习心得,自己的理解. 1. http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要 ...

  2. ArcGIS API for JavaScript 入门教程[3] 你看得到:数据与视图分离

    这篇开始正式讲API. 数据和视图分离不是什么奇怪的事情了,这是一个著名的设计--数据与视图分开. 转载注明出处,博客园/CSDN/B站:秋意正寒. 目录:https://www.cnblogs.co ...

  3. CentOS7 安装Redis 单机版

    1,下载Redis4.0.9 进入Redis中文网的下载页面 http://www.redis.cn/download.html 2,上传压缩包到linux系统 cd /user/local/java ...

  4. java jackson 忽略不存在的属性字段 和 按照属性名转json

    @JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibi ...

  5. hystrix 结果缓存机制(5)

    hystrix支持将一个请求结果缓存起来,下一个具有相同key的请求将直接从缓存中取出结果,减少请求开销.要使用hystrix cache功能 第一个要求是重写getCacheKey(),用来构造ca ...

  6. 解决VS2019中.net core WPF 暂时无法使用 Designer 的临时方法

    目录 解决 VS2019 中.net core WPF 暂时无法使用 Designer 的临时方法 安装 vs 2019 professional/enterprise版本 在vs的设置里,勾选.NE ...

  7. WebGL three.js学习笔记 自定义顶点建立几何体

    自定义顶点建立几何体与克隆 Three.js本身已经有很多的网格模型,基本已经够我们的使用,但是如果我们还是想自己根据顶点坐标来建立几何模型的话,Three.js也是可以的. 基本效果如图: 点击查看 ...

  8. ConcurrentDictionary并发字典知多少?

    背景 在上一篇文章你真的了解字典吗?一文中我介绍了Hash Function和字典的工作的基本原理. 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. https://doc ...

  9. 关于JAVA中Byte类型的取值范围的推论(*零为正数,-128在计算机中的表示方法...)

    先看一段推理<*一切都是在8个比特位的前提下,讨论二进制的符号位,溢出等等,才有意义*> +124:0111 1100 -124:1000 0100 +125:0111 1101 -125 ...

  10. FontAwesome 图标字体库的使用

    在前端开发中,许多新手常会遇见一个问题,参考的网页上有类似下图的图标,但在资源里却找不到对应的文件,这是因为这些网页使用了图标库.这里介绍一种常见的图标库——FontAwesome的使用. 1.登录F ...