前言

最近在做项目过程中使用到了如题并行方法,当时还是有点犹豫不决,因为平常使用不多, 于是借助周末时间稍微深入了下,发现我用错了,故此做一详细记录,希望对也不是很了解的童鞋在看到本文此文后不要再犯和我同样的错误。

并行遍历异步表象

这里我们就不再讲解该语法的作用以及和正常遍历处理的区别,网上文章比比皆是,我们直接进入主题,本文所演示程序在控制台中进行。可能大部分童鞋都是如下大概这样用的

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异步的更多相关文章

  1. 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). 其次是 ...

  2. 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 ...

  3. Parallel.ForEach , ThreadPool.QueueUserWorkItem

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. TPL(Task Parallel Library)多线程、并发功能

    The Task Parallel Library (TPL) is a set of public types and APIs in the System.Threading and System ...

  5. C# 多线程 Parallel.For 和 For 谁的效率高?那么 Parallel.ForEach 和 ForEach 呢?

    还是那句话:十年河东,十年河西,莫欺少年穷. 今天和大家探讨一个问题:Parallel.For 和 For 谁的效率高呢? 从CPU使用方面而言,Parallel.For 属于多线程范畴,可以开辟多个 ...

  6. Parallel.Foreach的基础知识

    微软的并行运算平台(Microsoft’s Parallel Computing Platform (PCP))提供了这样一个工具,让软件开发人员可以有效的使用多核提供的性能. Visual Stud ...

  7. Task/Parallel实现异步多线程

    代码: #region Task 异步多线程,Task是基于ThreadPool实现的 { //TestClass testClass = new TestClass(); //Action<o ...

  8. Parallel.Foreach

    随着多核时代的到来,并行开发越来越展示出它的强大威力! 使用并行程序,充分的利用系统资源,提高程序的性能.在.net 4.0中,微软给我们提供了一个新的命名空间:System.Threading.Ta ...

  9. [译]何时使用 Parallel.ForEach,何时使用 PLINQ

    原作者: Pamela Vagata, Parallel Computing Platform Group, Microsoft Corporation 原文pdf:http://download.c ...

随机推荐

  1. 计算机启动 Ubuntu系统初始化 SysV Systemd

    计算机启动过程 第一阶段:BIOS boot (bootstrap的缩写)来自一句谚语:"pull oneself up by one's bootstraps" 最早的时候,计算 ...

  2. eatwhatApp开发实战(五)

    上次我们为eatwhat添加了了删除功能和dialog对话框的介绍,今天我们来使用SQLite进行本地数据存储. 首先,我们定义一个SQL辅助类ShopInfoOpenHelper继承SQLiteOp ...

  3. 派生类Student的构造函数和析构函数 代码参考

    #include <iostream> #include <cstring> using namespace std; class Person { private: char ...

  4. Jenkins漏洞利用复现

    一.未授权访问 访问url: http://172.16.20.134:8080/script 命令执行 println "ls -al".execute().text 也可以利用 ...

  5. CE未知数值修改

    一样,用植物大战僵尸测试.来搜索修改向日葵生产阳光的CD值. 由于开始并不知道向日葵cd的初始值,所以用CE搜索未知的初始值 返回游戏,每次向日葵晃一下搜索一下减少的值. 锁定修改为0发现成功. 然后 ...

  6. Beta冲刺 —— 5.29

    这个作业属于哪个课程 软件工程 这个作业要求在哪里 Beta冲刺 这个作业的目标 Beta冲刺 作业正文 正文 github链接 项目地址 其他参考文献 无 一.会议内容 1.展示了每个人当天的成果. ...

  7. css引入方式和基本样式

    css的三种引入方式: 1.内嵌:直接在标签中添加style属性 格式:<标签名 style="样式1:样式值1:样式2=样式值2:"></标签名> 2.内 ...

  8. Java实现蓝桥杯历届试题回文数字

    历届试题 回文数字 时间限制:1.0s 内存限制:256.0MB 提交此题 问题描述 观察数字:12321,123321 都有一个共同的特征,无论从左到右读还是从右向左读,都是相同的.这样的数字叫做: ...

  9. Java实现 LeetCode 518 零钱兑换 II

    518. 零钱兑换 II 给定不同面额的硬币和一个总金额.写出函数来计算可以凑成总金额的硬币组合数.假设每一种面额的硬币有无限个. 示例 1: 输入: amount = 5, coins = [1, ...

  10. Java实现 蓝桥杯VIP 算法训练 字符串逆序

    问题描述 给定一个字符串,将这个串的所有字母逆序后输出. 输入格式 输入包含一个字符串,长度不超过100,字符串中不含空格. 输出格式 输出包含一个字符串,为上面字符串的逆序. 样例输入 tsinse ...