C# Parallel.ForEach vs Task.Run和Task.whalll

C# Parallel.ForEach vs Task.Run和Task.whalll,c#,async-await,parallel.foreach,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); }); List strings=新列表{“s1”、“s2”、“s3”}; Parallel.ForEach(字符串,s=> { 剂量测定

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

第1版:

List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
    DoSomething(s);
});
List strings=新列表{“s1”、“s2”、“s3”};
Parallel.ForEach(字符串,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);
List strings=新列表{“s1”、“s2”、“s3”};
列表任务=新列表();
foreach(字符串中的变量s)
{
Tasks.Add(Task.Run(()=>DoSomething));
}
等待任务。何时(任务);

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

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

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

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

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

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

但是,使用
任务有一个缺点。使用
并行循环运行
。对于每个
任务,都会创建一个循环,以避免产生超出需要的任务
Task.Run
将始终为每个项目创建一个任务(因为您正在执行此操作),但是
并行
类会成批工作,因此您创建的任务少于总工作项。这可以提供显著更好的总体性能,特别是当循环体的每个项都有少量工作时

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

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));
请注意,这也可以用以下较短的形式书写:

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

最后我做了这件事,因为读起来更容易:

  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);
List x=新列表();
foreach(myCollectionOfObject中的var s)
{
//注意这里没有等待。只需收集任务
x、 添加(s.DoSomethingAsync());
}
等待任务。WhenAll(x);

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

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

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}]");
        }
    }
}
使用系统;
使用System.Linq;
使用System.Threading.Tasks;
名称空间ParrelelleacHexample
{
班级计划
{
静态void Main(字符串[]参数)
{
var索引=新的int[]{1,2,3};
运行示例((前缀)=>Parallel.ForEach(索引,(i)=>DoSomethingAsync(i,前缀)),
“Parallel.Foreach”);
Console.ForegroundColor=ConsoleColor.Yellow;
WriteLine(“*您会注意到任务尚未运行,因为主线程未被阻塞*”;
WriteLine(“按任意键开始下一个示例…”);
Console.ReadKey();
运行示例((前缀)=>Task.WhenAll(index.Select(i=>DoSomethingAsync(i,前缀)).ToArray()).Wait(),
“Task.WhenAll”);
Console.WriteLine(“所有任务都已完成。按任意键关闭…”);
Console.ReadKey();
}
静态void运行示例(操作、字符串前缀)
{
Console.ForegroundColor=ConsoleColor.White;
Console.WriteLine($“{Environment.NewLine}起始于“{prefix}…”);
动作(前缀);
WriteLine($“{Environment.NewLine}已完成{prefix}{Environment.NewLine}”);
}
静态异步任务DoSomethingAsync(int i,字符串前缀)
{
等待任务。延迟(i*1000);
WriteLine($“Finished:{prefix}[{i}]”);
}
}
}
结果如下:

结论:

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


~Cheers

如果您使用
任务,我认为第二个代码片段几乎等于第一个代码片段。WaitAll
而不是
任务。whalll
。请注意,第二个代码片段将执行DoSomething(“s3”)三次,并且不会产生相同的结果!@Dan的可能重复:注意版本2使用了async/await,这意味着这是一个不同的问题。Async/await是在VS 2012中引入的,它是在可能的重复线程编写完成1.5年后引入的。请看一个很好的答案,我想知道您是否可以为我提供一个关于这个主题的好阅读材料?我的Parallel.ForEach构造正在使我的应用程序崩溃。我在里面做了一些繁重的图像处理。但是,当我添加Task.Run(()=>Parallel.ForEach(..)时;它停止了碰撞。你能解释一下原因吗?请注意,我将并行选项限制为系统上的内核数。如果
DoSomething
async void DoSomething
?那么
async Task DoSomething
?@ShawnMclean-您可以将async添加为:wait Task.Run(()=>parallel.ForEach(字符串,async s=>{wait DoSomething(s);});不应该是
wait Task.whalll(strings.Select(async s=>wait Task.Run(()=>DoSomething));
吗?我在返回任务(而不是等待)时遇到了问题,特别是当涉及
using
等语句来处理对象时。我的Parallel.ForEach调用导致我的UI崩溃,我添加了Task.Run(()=>Parallel.ForEach)(..);然后它解决了崩溃问题。这样,你正在做的任务是一个接一个地执行的,还是所有的任务都同时启动?据我所知,它们都是在我调用“DoSomethingAsync()”时启动的。但是,在调用WhenAll之前,没有任何东西会阻止它们。你是说当第一个“DoSomethingAsync()时?”调用?@ChrisM。它将被阻止,直到第一次等待DoSomethingAsync(),因为这是将执行转移回循环的原因。如果它是同步的,则返回