【问题标题】:Are Parallel.Invoke and Parallel.ForEach essentially the same thing?Parallel.Invoke 和 Parallel.ForEach 本质上是一样的吗?
【发布时间】:2012-06-07 16:50:01
【问题描述】:

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

编辑: 我应该更清楚;我在问这两种方法是否做同样的事情,因为如果没有,我想知道哪种方法更有效。

示例:我有一个包含 500 个键值的列表。目前我使用foreach 循环遍历列表(串行)并为每个项目执行工作。如果我想利用多核,我应该直接使用Parallel.ForEach 吗?
假设为了争论,我有一个包含 500 个代表的数组来处理这 500 个任务 - 调用 Parallel.Invoke 并给它一个包含 500 个代表的列表,最终效果会不会有所不同?

【问题讨论】:

    标签: c# multithreading parallel-processing parallel.foreach parallel.invoke


    【解决方案1】:

    Parallel.ForEach 遍历元素列表,可以对数组元素执行一些任务。

    例如。

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

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

    例如。

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

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

    【讨论】:

    • "Parallel.ForEach ... 可以对数组的元素执行一些任务。"那么如果我的任务是调用一个函数呢?与 Parallel.Invoke 在工作如何完成以及一个是否会优于另一个方面有什么真正的区别?
    • 1. Parallel.ForEach(val, (array) => Sum(array)); 正在调用函数 Sum(),所以是的,它可以调用函数。 2. 我认为这取决于你需要做什么。如果您在元素列表上调用单个函数而不是 Parallel.ForEach 似乎是一个不错的选择,但如果您要调用多个元素或单个元素的 List 上的一个函数而不是 Parallel.Invoke 将工作得最好。
    【解决方案2】:

    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

    【讨论】:

    • 谢谢plukich。在我的特殊情况下,我有一些“即发即弃”的工作要执行,所以我不关心返回值,甚至不关心工作的执行顺序。所以我纯粹好奇是否有任何区别/好处打电话给一个或另一个。
    • 根据我的经验,它们的行为相同。这可能比您想要的要多,但是如果您下载最新的 .NET Reflector 并打开 mscorlib.dll,您可以通过查看 System.Threading.Tasks 中的 Parallel 类来查看问题的确切答案。这就是我会做的。
    【解决方案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}"); }
    

    输出(在我的 PC 中,.NET 5,发布版本):

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

    Try it on Fiddle.

    【讨论】:

      【解决方案4】:

      我正在努力寻找一种好的措辞方式;但它们不是一回事。

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

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

      +1 也是一个好问题。

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

      【讨论】:

      • 感谢您的回复,拉斯。我理解方法签名方面的差异,我只是想知道它们是否在内部都使用相同的方法来仔细研究工作。
      • 在这种情况下;不,他们不是。 ForEach 使您可以在调用 Action 之前访问它; Invoke 就是这么做的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-31
      • 1970-01-01
      • 2011-12-29
      • 2021-06-22
      • 1970-01-01
      • 2016-10-28
      • 1970-01-01
      相关资源
      最近更新 更多