Tâche.Méthode WaitAll vs parallèle.Méthode Invoke

J'ai un exemple de code pour comparer le temps de traitement pour l'approche parallèle et L'approche de la tâche. Le but de cette expérience est de comprendre comment ils fonctionnent.

Donc mes questions sont:

  1. Pourquoi Parallel a travaillé plus vite que Task?
  2. mes résultats signifient-ils que je devrais utiliser Parallel au lieu de Task?
  3. Où dois-je utiliser Task et where Parallel?
  4. Quels sont les avantages de L'utilisation de la tâche par rapport au parallèle?
  5. La tâche est-elle juste une enveloppe pour ThreadPool.Méthode QueueUserWorkItem?

        public Task SomeLongOperation()
        {
            return Task.Delay(3000);
        }
    
        static void Main(string[] args)
        {
            Program p = new Program();
            List<Task> tasks = new List<Task>();
    
            tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
            tasks.Add(Task.Factory.StartNew(() => p.SomeLongOperation()));
    
            var arr = tasks.ToArray();
    
            Stopwatch sw = Stopwatch.StartNew();
            Task.WaitAll(arr);
            Console.WriteLine("Task wait all results: " + sw.Elapsed);
            sw.Stop();
    
            sw = Stopwatch.StartNew();
            Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
            Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
            sw.Stop();
    
            Console.ReadKey();
        }
    

Voici mes résultats de traitement: résultat

Modifier:

Code Modifié pour ressembler à ceci:

    Program p = new Program();
    Task[] tasks = new Task[2];

    Stopwatch sw = Stopwatch.StartNew();
    tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
    tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());

    Task.WaitAll(tasks);
    Console.WriteLine("Task wait all results: " + sw.Elapsed);
    sw.Stop();

    sw = Stopwatch.StartNew();
    Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
    Console.WriteLine("Parallel invoke results: " + sw.Elapsed);
    sw.Stop();

Mes nouveaux résultats:

de nouveaux résultats

Modifier 2: Quand j'ai remplacé le code par Parallel.Invoquer pour être le premier et la tâche.WaitAll pour être deuxième la situation a été changée cardinalement. Maintenant parallèle est plus lent. Il me fait penser à l'inexactitude de mes estimations. J'ai changé de code pour ressembler à ce:

Program p = new Program();
Task[] tasks = new Task[2];

Stopwatch sw = null;
for (int i = 0; i < 10; i++)
{
    sw = Stopwatch.StartNew();
    Parallel.Invoke(() => p.SomeLongOperation(), () => p.SomeLongOperation());
    string res = sw.Elapsed.ToString();
    Console.WriteLine("Parallel invoke results: " + res);
    sw.Stop();
}

for (int i = 0; i < 10; i++)
{
    sw = Stopwatch.StartNew();
    tasks[0] = Task.Factory.StartNew(() => p.SomeLongOperation());
    tasks[1] = Task.Factory.StartNew(() => p.SomeLongOperation());
    Task.WaitAll(tasks);
    string res2 = sw.Elapsed.ToString();
    Console.WriteLine("Task wait all results: " + res2);
    sw.Stop();
}

Et voici mes nouveaux résultats:

entrez la description de l'image ici

entrez la description de l'image ici

Maintenant, je peux suggérer que cette expérience est beaucoup plus clair. Les résultats sont presque les mêmes. Parfois parallèle et parfois la tâche est plus rapide. Maintenant, mes questions sont:

1. Où dois-je utiliser Task et where Parallel?

2. Quels avantages de L'utilisation de la tâche par rapport au parallèle?

3. Est-ce que la tâche est juste une enveloppe pour ThreadPool.QueueUserWorkItem méthode?

Toute information utile pouvant clarifier ces questions est la bienvenue.

22
demandé sur Igor Lozovsky 2013-04-19 13:48:20

2 réponses

MODIFIER en tant que de cet article à partir de MSDN:

Parallel et Task sont des wrappers pour ThreadPool. L'appel parallèle attend également que toutes les tâches soient terminées.

En rapport avec vos questions:

L'utilisation de Task, Parallel ou ThreadPool dépend de la granularité du contrôle que vous devez avoir sur l'exécution de vos tâches parallèles. Je suis personnellement habitué à Task.Factory.StartNew(), mais c'est une opinion personnelle. La même chose concerne ThreadPool.QueueUserWorkItem()

Informations Supplémentaires: Le premier appel à Parallel.Invoke() et de la Tâche.Usine.StartNew () peut être plus lent en raison de l'initialisation interne.

6
répondu Stephen Reindl 2013-04-19 11:58:42

Si vous démarrez des tâches non génériques (c'est-à-dire "tâches nulles sans valeur de retour") et immédiatement Wait pour elles, utilisez Parallel.Invoke à la place. Votre intention est immédiatement claire pour le lecteur.

Utiliser des tâches Si:

  • vous n'Attendez pas immédiatement
  • Vous avez besoin de valeurs de retour
  • Vous devez donner des paramètres aux méthodes appelées
  • Vous avez besoin de la fonctionnalité TaskCreationOptions
  • Vous avez besoin de la fonctionnalité CancellationToken ou TaskScheduler et ne voulez pas utiliser ParallelOptions
  • , fondamentalement, si vous voulez plus d'options ou de contrôle

Oui, vous pouvez contourner certains d'entre eux, par exemple Parallel.Invoke(() => p.OpWithToken(CancellationToken) mais cela obscurcit votre intention. Parallel.Invoke est pour faire un tas de travail en utilisant autant de puissance CPU que possible. C'est fait, ça ne bloque pas, et vous le savez à l'avance.


Vos tests sont horribles. Le drapeau rouge serait que votre longue action est d'attendre 3000 millisecondes, mais vos tests prennent moins d'un dixième de milliseconde.

Task.Factory.StartNew(() => p.SomeLongOperation());

StartNew prend un Action, et s'exécute dans une nouvelle principal Task. L'action () => SomeLongOperation() crée une sous-tâche Task. Une fois cette sous-tâche créée (non terminée), l'appel à SomeLongOperation() retourne et l'Action est terminée. Donc le principal Task est déjà terminé après une dixième milliseconde, alors que les deux sous-tâches auxquelles vous n'avez aucune référence sont toujours en cours d'exécution en arrière-plan. le chemin parallèle crée également deux sous-tâches, qu'il ne suit pas du tout, et retourne.

La manière correcte serait tasks[0] = p.SomeLongOperation();, qui assigne une tâche en cours d'exécution au tableau. Puis WaitAll vérifie la finition de cette tâche.

2
répondu Wolfzoon 2018-02-06 11:23:48