Parallel.Invoke 和 Parallel.ForEach 本质上是一回事

本文关键字:Parallel 一回事 本质上 ForEach Invoke | 更新日期: 2023-09-27 18:36:04

我所说的"同样的事情"是指这两个操作基本上做相同的工作,它只是归结为根据你必须使用的内容调用哪个更方便?(即委托列表或要迭代的内容列表)? 我一直在搜索MSDN,StackOverflow和各种随机文章,但我还没有找到明确的答案。

编辑: 我应该更清楚;我问这两种方法是否做同样的事情,因为如果他们不这样做,我想了解哪种方法更有效。

示例:我有一个包含 500 个键值的列表。目前,我使用一个foreach循环来遍历列表(串行)并为每个项目执行工作。 如果我想利用多个内核,我应该简单地使用Parallel.ForEach吗?
为了论证起见,假设我有一个 500 个代表的数组来处理这 500 个任务 - 净效果会不会有什么不同的调用Parallel.Invoke并给出一个包含 500 个代表的列表?

Parallel.Invoke 和 Parallel.ForEach 本质上是一回事

Parallel.ForEach

历元素列表,可以对数组的元素执行一些任务。

例如。

Parallel.ForEach(val, (array) => Sum(array));

Parallel.Invoke可以并行调用许多函数。

例如。

Parallel.Invoke(
() => doSum(array),
() => doAvg(array),
() => doMedian(array));

从上面的示例中,您可以看到它们在功能上有所不同。 ForEach循环访问List元素并并行对每个元素执行一项任务,而Invoke可以在单个元素上并行执行许多任务

Parallel.Invoke 和 Parallel.ForEach(用于执行操作时)的功能相同,尽管是的,有人特别希望集合是一个数组。 请考虑以下示例:

List<Action> actionsList = new List<Action>
            {
                () => Console.WriteLine("0"),
                () => Console.WriteLine("1"),
                () => Console.WriteLine("2"),
                () => Console.WriteLine("3"),
                () => Console.WriteLine("4"),
                () => Console.WriteLine("5"),
                () => Console.WriteLine("6"),
                () => Console.WriteLine("7"),
                () => Console.WriteLine("8"),
                () => Console.WriteLine("9"),
            };
            Parallel.ForEach<Action>(actionsList, ( o => o() ));
            Console.WriteLine();
            Action[] actionsArray = new Action[]
            {
                () => Console.WriteLine("0"),
                () => Console.WriteLine("1"),
                () => Console.WriteLine("2"),
                () => Console.WriteLine("3"),
                () => Console.WriteLine("4"),
                () => Console.WriteLine("5"),
                () => Console.WriteLine("6"),
                () => Console.WriteLine("7"),
                () => Console.WriteLine("8"),
                () => Console.WriteLine("9"),
            };
            Parallel.Invoke(actionsArray);
            Console.ReadKey();

代码在一次运行时生成此输出。 它的输出通常每次都以不同的顺序进行。

0 5 1 6 2 7 3 8 4 9

0 1 2 4 5 6 7 8 9 3

令人惊讶的是,不,它们不是一回事。它们的根本区别在于它们在异常情况下的行为方式:

  1. Parallel.ForEach(以及Parallel.For和即将到来的Parallel.ForEachAsync会很快失败。发生异常后,它不会启动任何新的并行工作,并将在所有当前正在运行的委托完成后立即返回。
  2. Parallel.Invoke总是调用所有操作,无论其中的某些(或全部)操作是否失败。

为了演示此行为,让我们并行运行 1,000 个操作,每三个操作中有一个操作失败:

int c = 0;
Action[] actions = Enumerable.Range(1, 1000).Select(n => new Action(() =>
{
    Interlocked.Increment(ref c);
    if (n % 3 == 0) throw new ApplicationException();
})).ToArray();
try { c = 0; Parallel.For(0, actions.Length, i => actions[i]()); }
catch (AggregateException aex)
{ Console.WriteLine($"Parallel.For, Exceptions: {aex.InnerExceptions.Count}/{c}"); }
try { c = 0; Parallel.ForEach(actions, action => action()); }
catch (AggregateException aex)
{ Console.WriteLine($"Parallel.ForEach, Exceptions: {aex.InnerExceptions.Count}/{c}"); }
try { c = 0; Parallel.Invoke(actions); }
catch (AggregateException aex)
{ Console.WriteLine($"Parallel.Invoke, Exceptions: {aex.InnerExceptions.Count}/{c}"); }

输出(在我的电脑、.NET 5 发布版本中):

Parallel.For, Exceptions: 5/12
Parallel.ForEach, Exceptions: 5/11
Parallel.Invoke, Exceptions: 333/1000

在小提琴上试试。

> 我试图找到一种很好的措辞方式;但它们不是一回事。

原因是,Invoke 处理操作数组,ForEach 处理操作列表(特别是 IEnumerable);列表与机制中的数组明显不同,尽管它们公开了相同类型的基本行为。

我不能说差异实际上意味着什么,因为我不知道,所以请不要接受这个答案(除非你真的也想要!)但我希望它能唤起某人对机制的记忆。

+1 也是一个好问题。

编辑;我只是想到还有另一个答案;调用可以处理操作的动态列表;但 Foreach 可以使用通用 IEnumerable 的动作,并使您能够使用条件逻辑,逐个动作;因此,您可以在每次 Foreach 迭代中说 Action.Invoke() 之前测试条件。