深入了解C#(TPL)之Parallel.ForEach异步
前言
最近在做项目过程中使用到了如题并行方法,当时还是有点犹豫不决,因为平常使用不多, 于是借助周末时间稍微深入了下,发现我用错了,故此做一详细记录,希望对也不是很了解的童鞋在看到本文此文后不要再犯和我同样的错误。
并行遍历异步表象
这里我们就不再讲解该语法的作用以及和正常遍历处理的区别,网上文章比比皆是,我们直接进入主题,本文所演示程序在控制台中进行。可能大部分童鞋都是如下大概这样用的
Parallel.ForEach(Enumerable.Range(, ), index =>
{
Console.WriteLine(index);
});

我们采取并行方式遍历10个元素,然后结果也随机打印出10个元素,一点毛病也没有。然而我是用的异步方式,如下:
Parallel.ForEach(Enumerable.Range(, ), async index =>
{
await AsyncTask(index);
});
static async Task<int> AsyncTask(int i)
{
await Task.Delay(); var calculate = i * ; Console.WriteLine(calculate); return calculate;
}

我们只是将并行操作更改为了异步形式,然后对每个元素进行对应处理,打印无序结果,一切也是如我们所期望,接下来我再来看一个例子,经过并行异步处理后猜猜最终字典中元素个数可能或一定为多少呢?
var dicts = new ConcurrentDictionary<string, int>(); Parallel.ForEach(Enumerable.Range(, ), async index =>
{
var result = await AsyncTask(index); dicts.TryAdd(index.ToString(), result);
}); Console.WriteLine($"element count in dictionary {dicts.Count}");

如果对该并行方法没有深入了解的话,大概率都会猜错,我们看到字典中元素为0,主要原因是用了异步后引起的,为何会这样呢?我们首先从表象上来分析,当我们在控制台上对并行方法用了异步后,你会发现编译器会告警(主函数入口已用异步标识),如下:

接下来我们再来看看调用该并行异步方法的最终调用构造,如下:
public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body);
第二个参数为内置委托Action,所以我们也可以看出并不能用于异步,因为要是异步至少也是Func<Task>,比如如下方法参数形式
static async Task AsyncDemo(Func<int,Task> func)
{
await func();
}
并行遍历异步本质
通过如上表象的分析我们得出并行遍历方法应该是并不支持异步(通过最终结果分析得知,表述为不能用于异步更恰当),但是在实际项目开发中我们若没有注意到该方法的构造很容易就会误以为支持异步,如我一样写完也没报错,也就草草了事。那么接下来我们反编译看下最终实际情况会是怎样的呢。

进入主函数,我们已将主函数进行异步标识,所以将主函数放在状态机中执行(状态机类,<Main>d_0),这点我们毫无保留的赞同,接下来实例化字典,并通过并行遍历异步处理元素集合并将其结果尝试放入到字典中

由上我们可以看到主函数是在状态机中运行且构造为AsyncTaskMethodBuilder,当我们通过并行遍历异步处理时每次都会实例化一个状态机类即如上<<Main>b__0>d,但我们发现此状态机的构造是AsyncVoidMethodBuilder,利用此状态机类来异步处理每一个元素,如下

最终调用AsyncTask异步方法,这里我就不再截图,同样也是生成一个此异步方法的状态机类。稍加分析想必我们已经知晓结果,AsyncTaskMethodBuilder指的就是(async task),而AsyncVoidMethodBuilder指的是(async void),所以对并行遍历异步操作是将其隐式转换为async void,而不是async task,这也和我们从其构造为Action得出的结论一致,我们知道(async void)仅限于基于事件的处理程序(常见于客户端应用程序),其他情况避免用async void,也就是说将返回值放在Task或Task<T>中。当并行执行任务时,由于返回值为void,不会等待操作完成,这也就不难解释为何字典中元素个数为0。
总结
当时并没有过多的去了解,只是想当然的认为用了异步也没出现编译报错,但是又由于没怎么用过,我还是抱着怀疑的态度,于是再深究了下,发现用法是大错特错。通过构造仅接受为Action委托,这也就意味着根本无法等待异步操作完成,之所以能接受异步索引其本质是隐式转换为(async void),从另外一个角度看,异步主要用于IO密集型,而并行处理用于CPU密集型计算,基于此上种种一定不能用于异步,否则结果你懂的。
深入了解C#(TPL)之Parallel.ForEach异步的更多相关文章
- C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)
学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...
- Task C# 多线程和异步模型 TPL模型 【C#】43. TPL基础——Task初步 22 C# 第十八章 TPL 并行编程 TPL 和传统 .NET 异步编程一 Task.Delay() 和 Thread.Sleep() 区别
Task C# 多线程和异步模型 TPL模型 Task,异步,多线程简单总结 1,如何把一个异步封装为Task异步 Task.Factory.FromAsync 对老的一些异步模型封装为Task ...
- Parallel.ForEach , ThreadPool.QueueUserWorkItem
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- TPL(Task Parallel Library)多线程、并发功能
The Task Parallel Library (TPL) is a set of public types and APIs in the System.Threading and System ...
- C# 多线程 Parallel.For 和 For 谁的效率高?那么 Parallel.ForEach 和 ForEach 呢?
还是那句话:十年河东,十年河西,莫欺少年穷. 今天和大家探讨一个问题:Parallel.For 和 For 谁的效率高呢? 从CPU使用方面而言,Parallel.For 属于多线程范畴,可以开辟多个 ...
- Parallel.Foreach的基础知识
微软的并行运算平台(Microsoft’s Parallel Computing Platform (PCP))提供了这样一个工具,让软件开发人员可以有效的使用多核提供的性能. Visual Stud ...
- Task/Parallel实现异步多线程
代码: #region Task 异步多线程,Task是基于ThreadPool实现的 { //TestClass testClass = new TestClass(); //Action<o ...
- Parallel.Foreach
随着多核时代的到来,并行开发越来越展示出它的强大威力! 使用并行程序,充分的利用系统资源,提高程序的性能.在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Ta ...
- [译]何时使用 Parallel.ForEach,何时使用 PLINQ
原作者: Pamela Vagata, Parallel Computing Platform Group, Microsoft Corporation 原文pdf:http://download.c ...
随机推荐
- 【转】团队项目的Git分支管理规范
原文地址: http://blog.jboost.cn/git-branch.html 分支管理 创建项目时(一般是服务型项目,工具型或辅助型项目可以简单一些),会针对不同环境创建三个常设分支: de ...
- linux-offen-used-commands
文件系统 cd 进入目录 ls 列出目录信息,ls -al (或 ll)列出详细信息 touch 新建文件 mkdir 新建目录 rm 删除文件或目录 cp 复制 mv 移动(或重命名) 搜索.查找. ...
- 才华能力出众的ReentrantLock
主要内容 1. synchronized介绍 2. ReentrantLock介绍 3. ReentrantLock和synchronized的可伸缩性比较 4. Condition变量 5. Ree ...
- Java IO(九)FilterInputStream 和 FilterOutputStream
Java IO(九)FilterInputStream 和 FilterOutputStream 一.介绍 FilterInputStream 和 FilterOutputStream 是过滤字节输入 ...
- HttpServletRequestWrapper 类&过滤指定文字
HttpServletWrapper 和 HttpServletResponseWrapper 1). Servlet API 中提供了一个 HttpServletRequestWrapper 类来包 ...
- 分布式项目开发-spring-dao.xml基础配置
基础步骤: 1 数据源 2 sqlSessionFactory 3 MapperScan 打包. db.properties文件 db.username=root db.password=123456 ...
- (Java实现) 整数区间
一.题目描述 给n个区间,形式为[a, b],a和b均为整数,且a < b. 求一个最小的整数点的集合,使得每个区间至少2个不同的元素(整数点)属于这个集合. 求这个集合的元素个数. 输入 第1 ...
- Java实现 蓝桥杯 历届试题 小计算器
历届试题 小计算器 时间限制:1.0s 内存限制:256.0MB 问题描述 模拟程序型计算器,依次输入指令,可能包含的指令有 1. 数字:'NUM X',X为一个只包含大写字母和数字的字符串,表示一个 ...
- Java实现 蓝桥杯VIP 算法训练 麦森数
算法训练 麦森数 时间限制:1.0s 内存限制:256.0MB 问题描述 形如2P-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2P-1不一定也是素数.到1998年底, ...
- Java实现蓝桥杯分金币
分金币 圆桌旁坐着n个人,每人有一定数量的金币,金币总数能被n整除.每个人可以给他左右相邻的人一些金币, 最终使得每个人的金币数目相等.你的任务是求出被转手的金币数量的最小值. 比如,n=4,且4个人 ...