web-dev-qa-db-fra.com

Parallel.ForEach vs Task.Run et Task.WhenAll

Quelles sont les différences entre l'utilisation de Parallel.ForEach ou de Task.Run () pour démarrer un ensemble de tâches de manière asynchrone?

Version 1:

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

Version 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);
118
Petter T

Dans ce cas, la deuxième méthode attendra de manière asynchrone que les tâches soient terminées au lieu d'être bloquées.

Cependant, il y a un inconvénient à utiliser Task.Run dans une boucle: avec Parallel.ForEach, il existe un Partitioner qui est créé pour éviter de créer plus de tâches que nécessaire. Task.Run créera toujours une seule tâche par élément (puisque vous le faites), mais les lots de la classe Parallel fonctionnent afin que vous créiez moins de tâches que le nombre total d'éléments de travail. Cela peut améliorer considérablement les performances globales, en particulier si le corps de la boucle nécessite peu de travail par élément.

Si tel est le cas, vous pouvez combiner les deux options en écrivant:

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

Notez que cela peut également être écrit dans cette forme plus courte:

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

La première version bloquera le thread appelant de manière synchrone (et exécutera certaines des tâches sur celui-ci).
S'il s'agit d'un thread d'interface utilisateur, cela gèlera l'interface utilisateur.

La deuxième version exécute les tâches de manière asynchrone dans le pool de threads et libère le thread appelant jusqu'à ce qu'elles soient terminées.

Il existe également des différences dans les algorithmes de planification utilisés.

Notez que votre deuxième exemple peut être réduit à

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

J'ai vu Parallel.ForEach utilisé de manière inappropriée et j'ai pensé qu'un exemple dans cette question pourrait être utile.

Lorsque vous exécutez le code ci-dessous dans une application console, vous verrez que les tâches exécutées dans Parallel.ForEach ne bloquent pas le thread appelant. Cela pourrait vous convenir si vous ne vous souciez pas du résultat (positif ou négatif), mais si vous en avez besoin, vous devez vous assurer d'utiliser 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}]");
        }
    }
}

Voici le résultat:

enter image description here

Conclusion:

L'utilisation de Parallel.ForEach avec une tâche ne bloquera pas le thread appelant. Si vous vous souciez du résultat, veillez à attendre les tâches.

~ A bientôt

0
Rogala

J'ai fini par le faire, car c'était plus facile à lire:

  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);
0
Chris M.