前言

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

并行遍历异步表象

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

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. 【软工】[技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE

    [技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE 官方文档与重要参考资料 官方demo 官方API调用样例 Playground 官方API Doc,但其搜索框不支持模 ...

  2. Python数据分析:pandas玩转Excel(三)

    将对象写入Excel工作表. 要将单个对象写入 Excel .xlsx 文件,只需指定目标文件名即可.要写入多个工作表,必须创建具有目标文件名的ExcelWriter对象,并在文件中指定要写入的工作表 ...

  3. itext7史上最全实战总结

    1. itext7史上最全实战总结 1.1. 前言 最近有个需求需要我用Java手动写一份PDF报告,经过考察几种pdf开源代码,最终选取了itext7,此版本为7.1.11,由于发现网上关于该工具的 ...

  4. 利用Python网络爬虫采集天气网的实时信息—BeautifulSoup选择器

    相信小伙伴们都知道今冬以来范围最广.持续时间最长.影响最重的一场低温雨雪冰冻天气过程正在进行中.预计,今天安徽.江苏.浙江.湖北.湖南等地有暴雪,局地大暴雪,新增积雪深度4-8厘米,局地可达10-20 ...

  5. Java面向对象 类与对象与方法的储存情况

    栈.堆.方法区 类(含方法)储存在方法区 main函数入栈 堆里面存储方法区中类与方法的地址 main函数调用方法,找堆里面方法的地址,再从方法区找到对应函数,函数入栈,用完出栈 总结: 1.类.方法 ...

  6. Java实现 LeetCode 637 二叉树的层平均值(遍历树)

    637. 二叉树的层平均值 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组. 示例 1: 输入: 3 / \ 9 20 / \ 15 7 输出: [3, 14.5, 11] 解释: 第0层的 ...

  7. Java实现 蓝桥杯VIP 算法训练 最大值与最小值的计算

    输入11个整数,计算它们的最大值和最小值. 样例输入 0 1 2 3 4 5 6 7 8 9 10 样例输出 10 0 import java.util.Scanner; public class 最 ...

  8. Java实现 LeetCode 300 最长上升子序列

    300. 最长上升子序列 给定一个无序的整数数组,找到其中最长上升子序列的长度. 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,10 ...

  9. Java实现 LeetCode 235 二叉搜索树的最近公共祖先

    235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个 ...

  10. Java实现 洛谷 P1422 小玉家的电费

    import java.util.*; public class Main { public static void main(String[] args) { Scanner in = new Sc ...