Parallel.ForEach 与 ActionBlock

问题描述 投票:0回答:1

对于给定的

MaxDegreeOfParallelism
和需要处理的固定数量的对象(即在它们上执行某些代码),似乎
Parallel.ForEach
ActionBlock
同样有用。

选择其中之一时需要考虑哪些因素?

c# parallel-processing task-parallel-library parallel.foreach tpl-dataflow
1个回答
0
投票

是的,

Parallel.ForEach
ActionBlock<T>
都可以用于并行处理项目列表。在这两者之间,
Parallel.ForEach
是更自然的选择,因为它清楚地传达了其目的,并且在使用之前需要较少的研究。两者都有可能让您大吃一惊的问题。以下是一些应该记住的事情:

  1. Parallel.ForEach
    处理项目的顺序取决于
    source
    的类型。如果它是列表或数组,则顺序将非常特殊,因为
    Parallel.ForEach
    会将列表按范围进行分区,并为每个范围分配一个工作任务(范围分区)。所以你会看到要处理的项目如下:1, 26, 51, 76, 2, 27, 52, 77...,而不是自然的 1, 2, 3, 4, 5, 6, 7, 8 等。如果源是
    IEnumerable<T>
    ,则顺序将是正常的开始到结束。
    ActionBlock<T>
    按照您
    Post
    的顺序处理项目,因此不会出现任何意外。

  2. source
    IEnumerable<T>
    时,
    Parallel.ForEach
    默认使用 chunk 分区,这意味着它一次不会仅从
    source
    中获取一项。它将项目累积成小块,然后开始处理它们。例如,如果您的
    source
    BlockingCollection<T>
    ,这可能会让您感到惊讶。您将在集合中添加一个项目,但
    Parallel.ForEach
    不会立即处理它,您会想知道为什么。
    ActionBlock<T>
    将项目逐一放入自己的缓冲区中,因此不会有任何意外。

  3. 默认情况下,

    ActionBlock<T>
    MaxDegreeOfParallelism = 1
    (即没有并行性)。相反,默认情况下
    Parallel.ForEach
    具有
    MaxDegreeOfParallelism = -1
    (即无限并行度)。
    Parallel.ForEach
    具有迄今为止 最危险的默认设置,因为如果您忘记配置
    MaxDegreeOfParallelism
    ,它将很快使您的
    ThreadPool
    饱和。当
    ThreadPool
    饱和时,程序的其他并发操作将会卡顿。

  4. ActionBlock<T>
    具有令人讨厌的“设计”行为,会吞下
    OperationCancelledException
    抛出的任何
    action
    。因此,如果处理某个项目可能会因
    OperationCancelledException
    失败,即,如果在您的情况下此异常表示失败而不是取消,则
    ActionBlock<T>
    将愉快地完成,没有任何异常,就像什么都没发生一样,向您隐藏处理实际上失败的情况。

我之前提到的

Parallel.ForEach
的特殊性可以通过将
source
包装在适当的
Partitioner
中来轻松修复,如这个答案所示。他们还可以通过切换到更新的
Parallel.ForEachAsync
API 来进行更彻底的修复。尽管
Parallel.ForEachAsync
的名称中有
Async
,但它可以同样轻松高效地处理同步工作负载。只需从
ValueTask.CompletedTask
返回一个
body
,然后
Wait
得到结果
Task
。虽然没有记录,但
Parallel.ForEachAsync
采用的分块/分区策略并不令人惊讶。它按照自然的开始到结束顺序处理项目。它对于拥有
ThreadPool
也不太积极,因为默认情况下它的
MaxDegreeOfParallelism
等于
Environment.ProcessorCount
,这对于大多数情况来说是一个合理的默认值。当它的工作任务从 source 获取项目时,它是
 异步同步
的,因此如果源是空的
BlockingCollection<T>
则只有一个线程会被阻塞。它缺少
Parallel.ForEach
所具有的一些功能,例如破坏和获取
LowestBreakIteration
,但这些功能在实践中很少使用。

© www.soinside.com 2019 - 2024. All rights reserved.