《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并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...
随机推荐
- CentOS7系统操作httpd服务 - 开机启动/重启/查看状态
第一.启动.终止.重启 systemctl start httpd.service #启动 systemctl stop httpd.service #停止 systemctl restart htt ...
- 遍历数组 foreach
package com.java.array; public class Myforeach { public static void main(String[] ARGS){ /* int arr[ ...
- SpringBoot之旅第六篇-启动原理及自定义starter
一.引言 SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置. 在日常开发中,我们也会自 ...
- 【公告】MIP组件审核平台故障-影响说明
故障通报 2017年8月8日 下午14:11,由于机器故障原因,MIP组件审核平台无法提供服务. 2017年8月8日 下午16:46,服务恢复. 故障影响 2017年8月8日下午13:00-14:00 ...
- MIPCache 域名升级
一.MIPCache URL 是什么 举个例子,MIP 官网的 URL 为: https://www.mipengine.org 对应的 MIPCache 的 URL 为: https://mipca ...
- 从壹开始前后端分离【 .NETCore2.1 +Vue 2 +AOP+DI】框架之一 || 前言
缘起 作为一个.Net攻城狮已经4年有余了,一直不温不火,正好近来项目不是很忙,闲得无聊,搞一搞新技术,一方面是打发无聊的时间,一方面也是督促自己该学习辣!身边的大神都转行的转行,加薪的加薪,本人比较 ...
- 循环神经网络(Recurrent Neural Network,RNN)
为什么使用序列模型(sequence model)?标准的全连接神经网络(fully connected neural network)处理序列会有两个问题:1)全连接神经网络输入层和输出层长度固定, ...
- Redis学习——详解Redis配置文件(三)
一.Redis脚本简介 在我们介绍Redis的配置文件之前,我们先来说一下Redis安装完成后生成的几个可执行文件: redis-server .redis-cli .redis-benchmark ...
- SpringBoot框架与MyBatis集成,连接Mysql数据库
SpringBoot是一种用来简化新Spring应用初始搭建及开发过程的框架,它使用特定方式来进行配置,使得开发人员不再需要定义样板化的配置.MyBatis是一个支持普通SQL查询.存储和高级映射的持 ...
- CenOS_命令帮助
1.帮助 1.1man 基本语法: man[命令或配置文件](功能描述:获得帮助信息) 如:man ll 1.2help 基本语法: help 命令 (功能描述:获得 shell 内置命令的帮助信息) ...