前言

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

并行遍历异步表象

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

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. 永久激活idea

    申明:本教程 IntelliJ IDEA 破解补丁.激活码均收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除. idea版本为老版本2018版本,下载地址如下,激活方法和插件在压缩包中 ...

  2. Python数据科学利器

    每个工具都带有用来创造它的那种精神. -- 海森堡<物理学和哲学> Anaconda Anaconda是一个python的科学计算发行版,其附带了一大批常用的数据科学包,不用再使用pip安 ...

  3. 03.Django-ORM

    ORM 1. 数据库配置 配置使用sqlite3,mysql,oracle,postgresql等数据库 sqlite3数据库配置 DATABASES = { 'default': { # 默认使用的 ...

  4. Vulnerability of SSL to Chosen-Plaintext Attack 读书报告

    这篇文章讲述了在SSL上的选择明文攻击.我想分四个部分讲讲我对这篇文章的理解. 1.CPA的定义: 2.文章讲述SSL相关基本概念漏洞: 3.对SSL的CPA攻击的过程: 4.实现这种攻击的可能性以及 ...

  5. 【极客思考】计算机网络:Wireshark抓包分析TCP中的三次握手与四次挥手

    [摘要]本文重点分析计算机网络中TCP协议中的握手和挥手的过程. [前提说明] 前段时间突然看到了一篇关于TCP/IP模型的文章,心想这段时间在家里也用wireshark抓了点包,那么想着想着就觉得需 ...

  6. OAuth + Security - 3 - JWT令牌

    为什么使用JWT令牌 在上面的资源服务器中,通过配置,我们了解到,当我们拿着token去获取资源时,程序会先去调用远程认证服务器的端点去验证解析token,或者在本地解析校验token,这样毫无疑问, ...

  7. 【HBase】表的version

    建表.添加数据 Examples: hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40'] hbase> c ...

  8. (易忘篇)java基本语法难点2

    1.不同类型的一维数组元素的默认初始化值 整型元素 : 0 boolean型元素 : false 浮点型元素 : 0.0 char型元素 : 0或'\u0000',而非'0' 引用类型元素 : nul ...

  9. Java实现 LeetCode 999 车的可用捕获量(简单搜索)

    999. 车的可用捕获量 在一个 8 x 8 的棋盘上,有一个白色车(rook).也可能有空方块,白色的象(bishop)和黑色的卒(pawn).它们分别以字符 "R"," ...

  10. Java实现 LeetCode 535 TinyURL 的加密与解密(位运算加密)

    535. TinyURL 的加密与解密 TinyURL是一种URL简化服务, 比如:当你输入一个URL https://leetcode.com/problems/design-tinyurl 时,它 ...