200

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

版本 1:

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

版本 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);
4

4 回答 4

203

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

但是,在循环中使用有一个缺点 - Task.RunWith Parallel.ForEach,创建了一个Partitionerwhich 以避免执行不必要的任务。 Task.Run将始终为每个项目创建一个任务(因为您正在执行此操作),但Parallel班级批次工作,因此您创建的任务少于总工作项目。这可以提供明显更好的整体性能,尤其是在循环体每个项目的工作量很小的情况下。

如果是这种情况,您可以通过以下方式组合这两个选项:

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

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

await Task.Run(() => Parallel.ForEach(strings, DoSomething));
于 2013-09-30T20:17:35.553 回答
44

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

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

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

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

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s))));
于 2013-09-30T20:15:05.183 回答
4

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

当您在控制台应用程序中运行以下代码时,您将看到在 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}]");
        }
    }
}

结果如下:

在此处输入图像描述

结论:

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

于 2019-07-18T00:54:05.967 回答
0

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

  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);
于 2018-04-14T02:21:21.460 回答