使用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);
答案 0 :(得分:125)
在这种情况下,第二种方法将异步等待任务完成而不是阻塞。
但是,在循环中使用Task.Run
有一个缺点 - 使用Parallel.ForEach
时,会创建Partitioner
以避免执行超出必要的任务。 Task.Run
将始终为每个项目创建一个任务(因为您正在执行此操作),但Parallel
类批处理工作,因此您创建的任务少于总工作项。这可以提供明显更好的整体性能,特别是如果循环体每个项目的工作量很少。
如果是这种情况,您可以通过编写以下内容来组合两个选项:
await Task.Run(() => Parallel.ForEach(strings, s =>
{
DoSomething(s);
}));
请注意,这也可以用这种较短的形式写出:
await Task.Run(() => Parallel.ForEach(strings, DoSomething));
答案 1 :(得分:31)
第一个版本将同步阻止调用线程(并在其上运行一些任务) 如果它是一个UI线程,这将冻结UI。
第二个版本将在线程池中异步运行任务并释放调用线程,直到它们完成。
使用的调度算法也存在差异。
请注意,您的第二个示例可以缩短为
await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));
答案 2 :(得分:0)
我最终这样做了,因为感觉更容易阅读:
Entry
答案 3 :(得分:0)
我看到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一起使用不会阻塞调用线程。如果您关心结果,请确保等待任务。
〜干杯