Parallel.Invoke和Parallel.ForEach本质上是相同的吗?

25

我所说的“同一件事”是指这两个操作基本上做了相同的工作,只是根据你要处理的内容来决定哪一个更方便调用(例如代表列表或迭代对象列表)? 我已经在 MSDN、StackOverflow 和各种随机文章中搜索过,但还没有找到明确的答案。

编辑:我应该更清楚一些;我想知道这两种方法是否做了相同的事情,因为如果它们没有,我想了解哪种更有效率。

例如:我有一个包含 500 个键值对的列表。当前我使用一个 foreach 循环来遍历列表(串行),并对每个项目执行工作。如果我想利用多个核心,我是否应该简单地使用 Parallel.ForEach
假设出于论点的目的,我有一个包含 500 个委托的数组来执行这 500 个任务,那么调用 Parallel.Invoke 并给它一个包含 500 个委托的列表会产生任何不同的净效果吗?

4个回答

35

Parallel.ForEach通过遍历数组中的元素,并对其执行某些任务。

例如:

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

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

例如:

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

从上面的例子可以看出,它们在功能上有所不同。ForEach 迭代遍历一个元素的List 并且同时对每个元素执行一个任务,而 Invoke 可以在单个元素上并行执行多个任务


2
Parallel.ForEach可以在数组的元素上执行某些任务。那么,如果我的任务是调用一个函数呢?与Parallel.Invoke相比,在工作完成的方式和哪个更优秀方面是否有真正的区别? - Brad Gagne
  1. Parallel.ForEach(val, (array) => Sum(array)); 调用了函数 Sum(),所以它可以调用一个函数。
  2. 我认为这取决于你需要做什么。如果你需要在元素列表上调用单个函数,那么 Parallel.ForEach 似乎是一个不错的选择,但如果你需要在 List 的多个元素或单个元素上调用多个函数,则 Parallel.Invoke 是最好的选择。
- Mayank
“元素”是什么意思?这部分对我来说不太清楚。在一个元素上执行多个任务是什么意思?什么是单个元素? - Joseph Katzman

12

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

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


谢谢plukich。在我的情况下,我有一些“fire-and-forget”的工作需要完成,因此我不关心返回值甚至工作执行的顺序。所以我只是纯粹好奇是否调用其中一个会有任何区别/好处。 - Brad Gagne
根据我的经验,它们的行为是相同的。这可能需要更多的努力,但如果您下载最新的.NET Reflector并打开mscorlib.dll,您可以通过查看System.Threading.Tasks中的Parallel类来获得确切的答案。那就是我会做的事情。 - plukich

2

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

  1. Parallel.ForEach(以及{{link2:Parallel.For}}和即将推出的{{link3:Parallel.ForEachAsync}})快速失败。在发生异常后,它不会启动任何新的并行工作,并且只有在当前运行的所有委托完成后才会返回。
  2. {{link4: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

尝试在 Fiddle 上测试:Fiddle

0

我正在尝试找到一个好的措辞方式,但它们并不是同一件事。

原因是,Invoke适用于Action数组,而ForEach适用于List(特别是IEnumerable)中的Action;列表在机制上与数组有很大的不同,尽管它们展现了相同类型的基本行为。

我无法说出实际差异是什么,因为我不知道,所以请不要接受这个作为答案(除非你真的想这样做!),但我希望它能唤起某些人对机制的记忆。

+1 也是一个好问题。

编辑;我突然想到还有另一个答案;Invoke可以处理动态的Action列表;但是ForEach可以使用泛型IEnumerable的Actions,并且使您能够逐个Action使用条件逻辑;因此,在每个ForEach迭代中,您可以在说Action.Invoke()之前测试条件。


谢谢您的回复,Russ。我理解方法签名的区别,只是想知道它们在内部是否都使用相同的方法来处理工作。 - Brad Gagne
1
在这种情况下,它们并不相同。ForEach 在调用操作之前为您提供访问权限;而 Invoke 则只是执行操作。 - Russ Clarke

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