Parallel.ForEach vs Task.Run和Task.WhenAll

Pet*_*r T 144 c# async-await parallel.foreach

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

版本1:

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

版本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);
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 141

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

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

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

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

请注意,这也可以用这种较短的形式编写:

await Task.Run(() => Parallel.ForEach(strings, DoSomething));
Run Code Online (Sandbox Code Playgroud)

  • 我的 Parallel.ForEach 构造使我的应用程序崩溃。我在里面执行一些繁重的图像处理。但是,当我添加 Task.Run(()=&gt; Parallel.ForEach(....)); 时 它停止崩溃了。你能解释一下为什么吗?请注意,我将并行选项限制为系统上的核心数量。 (4认同)
  • 很好的答案,我想知道你是否能给我推荐一本关于这个主题的好的阅读材料? (3认同)
  • 如果DoSomething是异步void DoSomething怎么办? (2认同)

SLa*_*aks 33

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

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

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

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

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));
Run Code Online (Sandbox Code Playgroud)

  • 我应该在返回任务时(而不是等待时)遇到问题吗(不是当等待时),应该不是“等待Task.WhenAll(strings.Select(async s =&gt;等待Task.Run(()=&gt; DoSomething(s)));;”)涉及“使用”之类的语句来处理对象。 (2认同)