Parallel.ForEach已经过时了吗?老旧不流行了吗?

5

你好,

并行执行可以通过多种方式实现。从严格的手动“多线程”到使用微软创建的各种“辅助工具”。其中之一是Parallel类。

我的一个同事坚称Parallel.ForEach(或Parallel类整体)是“旧的”,不应该使用。相反,他说,应该使用异步操作。换句话说,应该使用Task.WhenAll()而不是Parallel.ForEach()

当被问及为什么不使用Parallel.ForEach(),当这正是所需的-同时执行多个昂贵的操作时,他回答说Parallel.ForEach()已经过时,微软建议在可能的情况下使用异步/等待。

我在MSDN和Stackoverflow以及我能找到的所有地方搜索,但我找不到任何指向必须使用异步/等待而不是.Parallel的必要性。虽然您经常可以通过交换这些工具来实现类似的结果,但这并不意味着Parallel.ForEach已经过时。还是过时了吗?

有没有某个声誉良好的机构(MSDN?)发布的一些“最佳实践”或“建议”,指出Parallel.ForEach()正在逐步淘汰,人们需要坚持创建、运行和等待任务?

请不要发布与Parallel VS Async相关的答案,因为这不是问题。

问题是:既然您可以使用异步/等待WhenAllWaitAll等)使任务并行运行,那么在.NET 4.5及更高版本中,'Parallel'类是否已过时,陈旧或不时尚?


7
如果他声称"微软建议",那么他应该能够展示这个建议。我自己还没有看到过这样的东西。 - Jon Skeet
2
你的朋友把并行和异步混淆了。 - dcastro
@JonSkeet,你看,我不想在实际研究之前就要求他提供证据,因为我不想显得敌对。如果你(或其他人)的回答是“当然,每个人都知道它已经过时了——我们只是为了老年人而保留它”,那该怎么办? - agfc
1
@AlexeiFimine:我认为你可以以非敌对的方式提问,这是完全合理的。 - Jon Skeet
@JonSkeet 最终我会问的。现在有你的帮助我正在为这次谈话做准备。充分准备总是更好的选择。 - agfc
3个回答

5

我认为Parallel.ForEach并不过时。

自从.NET 4引入了任务并行库(TPL)以来,微软已经区分了“数据并行”(例如Parallel.ForEach)和“任务并行”(Task。来自MSDN的介绍:

  • “数据并行是指在源集合或数组中同时(也就是并行)对元素执行相同的操作。”
  • “[T]ask parallelism是指一个或多个独立任务同时运行。”

(我强调了一下。像dcastro在上面评论的那样“你的朋友把并行和异步混淆了。”

这两种类型的并行/并发追求不同的目标,因此TPL为它们提供了不同的功能。

从概念上讲,Task.WhenAll 属于任务并行类别,因此我认为它不会使属于另一个(数据并行)类别的内容过时。


谢谢。在我的情况下,我调用多个不同来源的外部调用。这些调用彼此完全独立。但是,在所有调用完成之前,我无法继续进行。Parallel.ForEach和Task.WhenAll都可以正常工作。然而,我发现ParallelForEach更优雅,代码量更少。他认为,是的,也许使用它并没有错,但首选方法是Task.WhenAll()。 - agfc
@AlexeiFimine:关于意见分歧的一些通用建议:询问你的同事为什么声称Task.WhenAll更好。虽然在这种情况下往往没有明确的对与错,但人们应该能够理性地支持自己的观点。(请参见Jon Skeet上面的评论。)如果他的论点说服了你(即它们比你目前的观点更好),那就改变你的代码。如果你认为你有更好的论点(使用Parallel.ForEach编写更优雅的代码),那就坚持你的代码版本。 - stakx - no longer contributing

2
Parallel.ForEach(以及整个PLINQ)具有async语言支持所没有的能力。
例如,您可以限制并行度(例如,要处理100个项目,但每次最多只能处理10个)。因此,它不是过时的。
从根本上讲,async是使并发操作更容易编写的技术,而没有任何线程假设。 PLINQ是利用多个核心进行计算的技术。
我怀疑您的同事对直接使用任务并行库(TPL)在语言中与async的返回类型之外基本上是不必要的有太多解读。但是,PLINQ始终是TPL上的不同层。如果有什么区别,那就是PLINQ和async是两种不同的方式,用于不同的目的来利用TPL。

DegreeOfParallelism很不错。我一直觉得它的实现方式非常有用。有时候,我甚至会像在#If Debug中那样使用它进行测试和调试。.DegreeOfParallelism = 1 - agfc

2
asyncawait 完全与并行无关。它们是用于使现有异步 API 更易于使用和公开的技术。实际上,asyncawait 不会启动并行或并发。事实上,await 通过等待已经运行的内容来结束并行处理

Parallel.ForEach 用于在多个核心上以相同方式处理一组同类项。您可以通过生成大量任务来模拟 Parallel.ForEach。但这样做没有任何优点。实际上,这会引入低效和混淆代码。如果适用,使用 Parallel.ForEach 是更优秀的方法。

我认为你的同事不理解 await 只是等待,它不会启动任何操作。

对于 CPU 密集型工作,请使用 Parallel.* 和 PLINQ(大多数情况下)。


1
await Task.WhenAll(IEnumerable<Task> Tasks) 实际上会并行启动 Tasks 集合中的所有任务,然后等待它们全部完成。这个设计与 ParallelForEach 的作用完全相同。但是,为了实现这一点,我需要手动创建这些任务的列表。还有一个“退出异步”的问题。在我的情况下,使用 Parallel.ForEach 更直观、更清晰。 - agfc
@AlexeiFimine 这些任务可能在创建时就已经开始了,但不一定。Task.WhenAll不会启动它们,正如usr所指出的那样,await也不会启动它们。我们绝对不能保证这些任务将并行处理,也不能保证它们实际上是异步的——我们所知道的是,它们可能是同步和顺序执行的。再次强调,所有await做的就是等待。 - dcastro
@dcastro 感谢您的回复。使用await Task.WhenAll(mytasks)不是会同时启动所有任务并等待它们全部完成吗?您说“可能”。这是什么意思?我需要手动“启动”它们吗?在我的情况下,它们可以正常工作而无需手动启动,并且性能与Parallel.Foreach相同,因此它们肯定是并行运行的。 - agfc
1
@AlexeiFimine 再次强调,await Task.WhenAll 并不启动任何内容。我说“可能”,而不是肯定,因为大多数 API 返回的任务已经开始执行,例如 StreamReader.ReadToEndAsyncHttpClient.PostAsync。但是如果我创建一个新的任务并等待它却没有启动它,比如 await new Task(),那么方法就永远不会继续执行。但这不是重点,重点是,等待任务并不意味着它们会并行运行,即使它们可能会这样做。 - dcastro
@usr 是的,我误解了自己的代码 :) 你是对的,任务正在运行。我在前一行调用了它们。 - agfc
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接