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.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
令人惊讶的是,不,它们不是一回事。它们的根本区别在于它们在异常情况下的行为方式:
Parallel.ForEach
(以及Parallel.For
和即将到来的Parallel.ForEachAsync
)会很快失败。发生异常后,它不会启动任何新的并行工作,并将在所有当前正在运行的委托完成后立即返回。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() 之前测试条件。