C# Parallel.ForEach 与 Task.Run 和 Task.WhenAll

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19102966/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:05:58  来源:igfitidea点击:

Parallel.ForEach vs Task.Run and Task.WhenAll

c#async-awaitparallel.foreach

提问by Petter T

What are the differences between using Parallel.ForEach or Task.Run() to start a set of tasks asynchronously?

使用 Parallel.ForEach 或 Task.Run() 异步启动一组任务有什么区别?

Version 1:

版本 1:

List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
    DoSomething(s);
});

Version 2:

版本 2:

List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
    Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);

采纳答案by Reed Copsey

In this case, the second method will asynchronously wait for the tasks to complete instead of blocking.

在这种情况下,第二种方法将异步等待任务完成而不是阻塞。

However, there is a disadvantage to use Task.Runin a loop- With Parallel.ForEach, there is a Partitionerwhich gets created to avoid making more tasks than necessary. Task.Runwill always make a single task per item (since you're doing this), but the Parallelclass batches work so you create fewer tasks than total work items. This can provide significantly better overall performance, especially if the loop body has a small amount of work per item.

然而,Task.Run在循环中使用有一个缺点 - With Parallel.ForEach,有一个Partitioner被创建来避免执行不必要的任务。 Task.Run将始终为每个项目创建一个任务(因为您正在执行此操作),但是Parallel类批处理工作,因此您创建的任务少于总工作项目。这可以提供明显更好的整体性能,特别是如果循环体每个项目的工作量很小。

If this is the case, you can combine both options by writing:

如果是这种情况,您可以通过编写以下内容来组合这两个选项:

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));

Note that this can also be written in this shorter form:

请注意,这也可以写成以下较短的形式:

await Task.Run(() => Parallel.ForEach(strings, DoSomething));

回答by SLaks

The first version will synchronously block the calling thread (and run some of the tasks on it).
If it's a UI thread, this will freeze the UI.

第一个版本将同步阻塞调用线程(并在其上运行一些任务)。
如果它是一个 UI 线程,这将冻结 UI。

The second version will run the tasks asynchronously in the thread pool and release the calling thread until they're done.

第二个版本将在线程池中异步运行任务并释放调用线程,直到它们完成。

There are also differences in the scheduling algorithms used.

所使用的调度算法也存在差异。

Note that your second example can be shortened to

请注意,您的第二个示例可以缩短为

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));

回答by Chris M.

I ended up doing this, as it felt easier to read:

我最终这样做了,因为它更容易阅读:

  List<Task> x = new List<Task>();
  foreach(var s in myCollectionOfObject)
  {
      // Note there is no await here. Just collection the Tasks
      x.Add(s.DoSomethingAsync());
  }
  await Task.WhenAll(x);

回答by Rogala

I have seen Parallel.ForEach used inappropriately, and I figured an example in this question would help.

我看到 Parallel.ForEach 使用不当,我认为这个问题中的一个例子会有所帮助。

When you run the code below in a Console app, you will see how the tasks executed in Parallel.ForEach doesn't block the calling thread. This could be okay if you don't care about the result (positive or negative) but if you do need the result, you should make sure to use Task.WhenAll.

当您在控制台应用程序中运行以下代码时,您将看到在 Parallel.ForEach 中执行的任务如何不阻塞调用线程。如果您不关心结果(正面或负面),这可能没问题,但如果您确实需要结果,则应确保使用 Task.WhenAll。

using System;
using System.Linq;
using System.Threading.Tasks;

namespace ParrellelEachExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var indexes = new int[] { 1, 2, 3 };

            RunExample((prefix) => Parallel.ForEach(indexes, (i) => DoSomethingAsync(i, prefix)),
                "Parallel.Foreach");

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("*You'll notice the tasks haven't run yet, because the main thread was not blocked*");
            Console.WriteLine("Press any key to start the next example...");
            Console.ReadKey();

            RunExample((prefix) => Task.WhenAll(indexes.Select(i => DoSomethingAsync(i, prefix)).ToArray()).Wait(),
                "Task.WhenAll");
            Console.WriteLine("All tasks are done.  Press any key to close...");
            Console.ReadKey();
        }

        static void RunExample(Action<string> action, string prefix)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine($"{Environment.NewLine}Starting '{prefix}'...");
            action(prefix);
            Console.WriteLine($"{Environment.NewLine}Finished '{prefix}'{Environment.NewLine}");
        }


        static async Task DoSomethingAsync(int i, string prefix)
        {
            await Task.Delay(i * 1000);
            Console.WriteLine($"Finished: {prefix}[{i}]");
        }
    }
}

Here is the result:

结果如下:

enter image description here

在此处输入图片说明

Conclusion:

结论:

Using the Parallel.ForEach with a Task will not block the calling thread. If you care about the result, make sure to await the tasks.

将 Parallel.ForEach 与 Task 一起使用不会阻塞调用线程。如果您关心结果,请确保等待任务。

~Cheers

~干杯