Параллельный.ForEach против задачи.Запуск и задание.WhenAll

каковы различия между использованием Parallel.ForEach или задача.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);

3 ответов


в этом случае второй метод будет асинхронно ждать завершения задач вместо блокировки.

однако, есть недостаток в использовании Task.Run в цикле - с Parallel.ForEach, есть Partitioner который создается, чтобы избежать создания большего количества задач, чем необходимо. Task.Run всегда будет делать одну задачу за штуку (так как вы это делаете), но Parallel пакеты классов работают, поэтому вы создаете меньше задач, чем всего рабочих элементов. Это может обеспечить значительно лучшая общая производительность, особенно если тело цикла имеет небольшой объем работы на элемент.

если это так, вы можете объединить оба варианта, написав:

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

обратите внимание, что это также может быть записан в сокращенной форме:

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

первая версия будет синхронно блокировать вызывающий поток (и запускать некоторые из задач на нем).
Если это поток пользовательского интерфейса, это заморозит пользовательский интерфейс.

вторая версия будет запускать задачи асинхронно в пуле потоков и освобождать вызывающий поток, пока они не будут выполнены.

существуют также различия в используемых алгоритмах планирования.

обратите внимание, что ваш второй пример можно сократить до

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

Я закончил тем, что сделал это, так как было легче читать:

  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);