Parallel.ForEach 与 Task.Run 和 Task.WhenAll
Posted
技术标签:
【中文标题】Parallel.ForEach 与 Task.Run 和 Task.WhenAll【英文标题】:Parallel.ForEach vs Task.Run and Task.WhenAll 【发布时间】:2013-10-06 19:58:54 【问题描述】:使用 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);
【问题讨论】:
如果您使用Task.WaitAll
而不是Task.WhenAll
,我认为第二个代码片段几乎等于第一个。
另请注意,第二个将执行 DoSomething("s3") 三次,它不会产生相同的结果! ***.com/questions/4684320/…
Parallel.ForEach vs Task.Factory.StartNew的可能重复
@Dan:请注意,版本 2 使用 async/await,这意味着这是一个不同的问题。 Async/await 是在 VS 2012 中引入的,在编写可能的重复线程 1.5 年后。
@Nullius,从 C#5 开始,捕获的变量的行为符合预期,上面的循环对三个字符串中的每一个执行 DoSomething,例如***.com/questions/12112881/…。这个问题显然是针对 C#5 的,因为 Task.WhenAll 是在 C#5 和 .NET Framework 4.5 中引入的。所以第二个执行 DoSomething("s3") 三次是不正确的。
【参考方案1】:
第一个版本将同步阻塞调用线程(并在其上运行一些任务)。 如果是 UI 线程,这将冻结 UI。
第二个版本将在线程池中异步运行任务并释放调用线程,直到它们完成。
使用的调度算法也有区别。
请注意,您的第二个示例可以缩短为
await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s))));
【讨论】:
不应该是await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));
吗?我在返回任务(而不是等待)时遇到了问题,尤其是当涉及像 using
这样的语句来处理对象时。
我的 Parallel.ForEach 调用导致我的 UI 崩溃我添加了 Task.Run(()=> Parallel.ForEach (....) );到它,它解决了崩溃。
对于大量任务,选项 2 是否比选项 1 增加了额外的计算机开销?【参考方案2】:
在这种情况下,第二种方法将异步等待任务完成而不是阻塞。
但是,在循环中使用Task.Run
有一个缺点——使用Parallel.ForEach
,会创建一个Partitioner
,以避免执行不必要的任务。 Task.Run
将始终为每个项目创建一个任务(因为您正在这样做),但 Parallel
类批处理工作,因此您创建的任务比总工作项目少。这可以提供显着更好的整体性能,尤其是在循环体每个项目的工作量很小的情况下。
如果是这种情况,您可以通过以下方式组合这两个选项:
await Task.Run(() => Parallel.ForEach(strings, s =>
DoSomething(s);
));
请注意,这也可以写成更短的形式:
await Task.Run(() => Parallel.ForEach(strings, DoSomething));
【讨论】:
很好的答案,我想知道你是否可以为我指出关于这个主题的好的阅读材料? 我的 Parallel.ForEach 构造使我的应用程序崩溃。我在里面进行了一些繁重的图像处理。但是,当我添加 Task.Run(()=> Parallel.ForEach(....));它停止了崩溃。你能解释一下为什么吗?请注意,我将并行选项限制为系统上的内核数。 如果DoSomething
是async void DoSomething
怎么办?
async Task DoSomething
呢?
@ShawnMclean - 您可以将异步添加为:await Task.Run(() => Parallel.ForEach(strings, async s => await DoSomething(s); ));【参考方案3】:
我最终这样做了,因为它更容易阅读:
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);
【讨论】:
这样,您正在执行的任务是一个接一个地执行,还是WhenAll 一次启动所有任务? 据我所知,它们都是在我调用“DoSomethingAsync()”时启动的。然而,在调用 WhenAll 之前,没有任何东西可以阻止它们。 你的意思是当第一个“DoSomethingAsync()”被调用的时候? @ChrisM。它将被阻塞,直到第一次等待 DoSomethingAsync() 因为这会将执行转移回您的循环。如果它是同步的并且您返回一个任务,则所有代码将一个接一个地运行,并且 WhenAll 将等待所有任务完成【参考方案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.NewLineStarting 'prefix'...");
action(prefix);
Console.WriteLine($"Environment.NewLineFinished 'prefix'Environment.NewLine");
static async Task DoSomethingAsync(int i, string prefix)
await Task.Delay(i * 1000);
Console.WriteLine($"Finished: prefix[i]");
结果如下:
结论:
将 Parallel.ForEach 与 Task 一起使用不会阻塞调用线程。如果您关心结果,请确保等待任务。
【讨论】:
我认为这个结果很明显,因为您从 ForEach Body 启动 Async 方法(即使用新的 ThreadPool 线程而不等待结果)。这里我们必须调用 DoSomethingAsync(i, prefix).Result。 @Mic 虽然结果对您来说似乎很明显,但在 Web 应用程序中不当使用 Parallel.ForEach 的结果可能会导致服务器中出现一些严重问题,这些问题在加载到应用程序。这篇文章并不是说你不应该使用它,而是要确保那些使用它的人知道实际会发生什么。此外,您应该避免使用 .Result,因为您应该始终使用 async/await。Parallel.ForEach
不能用于异步方法调用。由于DoSomething
返回了一个未等待的任务,您应该在其上调用.Wait()
。现在您将看到 Parallel.ForEach
仅在所有工作完成后才返回。
@Bouke 答案的重点是帮助那些不了解差异的人。也就是说,您可以在 Parallel.ForEach 中使用任务,但它不会在主线程上执行。这并不意味着您应该这样做,但正如示例所演示的那样,它在代码中是允许的。这意味着任务中的代码在不同的线程上执行并且没有被阻塞。在某些情况下,有人可能希望发生这种情况,但他们应该知道正在发生的事情。以上是关于Parallel.ForEach 与 Task.Run 和 Task.WhenAll的主要内容,如果未能解决你的问题,请参考以下文章
Parallel.ForEach 与 Task.Factory.StartNew
Task.StartNew() 与 Parallel.ForEach :多个 Web 请求场景
在 .NET 3.5 中将 Parallel.Foreach 与分区器一起使用
Asp.Net 中有没有办法与运行 Parallel.Foreach 的后台线程进行通信