WaitAll vs WhenAll

335

Quelle est la différence entre Task.WaitAll()et Task.WhenAll()depuis le CTP Async? Pouvez-vous fournir un exemple de code pour illustrer les différents cas d'utilisation?

Yaron Levi
la source

Réponses:

504

Task.WaitAll bloque le thread actuel jusqu'à ce que tout soit terminé.

Task.WhenAllrenvoie une tâche qui représente l'action d'attendre que tout soit terminé.

Cela signifie qu'à partir d'une méthode asynchrone, vous pouvez utiliser:

await Task.WhenAll(tasks);

... ce qui signifie que votre méthode continuera une fois que tout sera terminé, mais vous ne lierez pas un fil de discussion jusqu'à ce moment-là.

Jon Skeet
la source
2
Après beaucoup de lecture, il est clair qu'async n'a rien à voir avec les discussions blog.stephencleary.com/2013/11/there-is-no-thread.html
Vince Panuccio
7
@Vince: Je pense que "rien à voir avec les threads" est une surestimation, et il est important de comprendre comment les opérations asynchrones interagissent avec les threads.
Jon Skeet
6
@KevinBui: Non, il ne devrait pas le bloquer - il attendra la tâche renvoyée par WhenAll, mais ce n'est pas la même chose que de bloquer le thread.
Jon Skeet
1
@JonSkeet Peut-être que la distinction précise entre ces deux est trop subtile pour moi. Pouvez-vous m'indiquer (et peut-être le reste d'entre nous) une référence qui fera clairement la différence?
CatShoes
125
@CatShoes: Pas vraiment - je l'ai expliqué aussi bien que possible. Je suppose que je pourrais faire une analogie - c'est comme la différence entre commander un plat à emporter et ensuite rester près de la porte en attendant qu'il arrive, vs commander un plat à emporter, faire d'autres choses et ensuite ouvrir la porte lorsque le courrier arrive ...
Jon Skeet
51

Alors que la réponse de JonSkeet explique la différence d'une manière généralement excellente, il existe une autre différence: la gestion des exceptions .

Task.WaitAlljette un AggregateExceptionlorsque l'une des tâches est lancée et vous pouvez examiner toutes les exceptions levées. Le awaitin await Task.WhenAlldéballe le AggregateExceptionet ne 'retourne' que la première exception.

Lorsque le programme ci-dessous s'exécute avec await Task.WhenAll(taskArray)la sortie est la suivante.

19/11/2016 12:18:37 AM: Task 1 started
19/11/2016 12:18:37 AM: Task 3 started
19/11/2016 12:18:37 AM: Task 2 started
Caught Exception in Main at 19/11/2016 12:18:40 AM: Task 1 throwing at 19/11/2016 12:18:38 AM
Done.

Lorsque le programme ci-dessous est exécuté avec Task.WaitAll(taskArray)la sortie est la suivante.

19/11/2016 12:19:29 AM: Task 1 started
19/11/2016 12:19:29 AM: Task 2 started
19/11/2016 12:19:29 AM: Task 3 started
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 1 throwing at 19/11/2016 12:19:30 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 2 throwing at 19/11/2016 12:19:31 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 3 throwing at 19/11/2016 12:19:32 AM
Done.

Le programme:

class MyAmazingProgram
{
    public class CustomException : Exception
    {
        public CustomException(String message) : base(message)
        { }
    }

    static void WaitAndThrow(int id, int waitInMs)
    {
        Console.WriteLine($"{DateTime.UtcNow}: Task {id} started");

        Thread.Sleep(waitInMs);
        throw new CustomException($"Task {id} throwing at {DateTime.UtcNow}");
    }

    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            await MyAmazingMethodAsync();
        }).Wait();

    }

    static async Task MyAmazingMethodAsync()
    {
        try
        {
            Task[] taskArray = { Task.Factory.StartNew(() => WaitAndThrow(1, 1000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(2, 2000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(3, 3000)) };

            Task.WaitAll(taskArray);
            //await Task.WhenAll(taskArray);
            Console.WriteLine("This isn't going to happen");
        }
        catch (AggregateException ex)
        {
            foreach (var inner in ex.InnerExceptions)
            {
                Console.WriteLine($"Caught AggregateException in Main at {DateTime.UtcNow}: " + inner.Message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Caught Exception in Main at {DateTime.UtcNow}: " + ex.Message);
        }
        Console.WriteLine("Done.");
        Console.ReadLine();
    }
}
tymtam
la source
13
la plus grande différence pratique est la gestion des exceptions. Vraiment? Parce que ce n'est vraiment pas la plus grande différence pratique. La plus grande différence pratique est que l'un est asynchrone et non bloquant alors que l'autre est bloquant. Ceci est beaucoup plus important que la façon dont il gère les exceptions.
Liam
5
Merci de l'avoir signalé. Cette explication a été utile dans le scénario sur lequel je travaille actuellement. Peut-être pas la "plus grande différence pratique", mais certainement un bon appel.
Urk
La gestion des exceptions étant la plus grande différence pratique pourrait être plus applicable à la comparaison entre await t1; await t2; await t3;vsawait Task.WhenAll(t1,t2,t3);
frostshoxx
1
Ce comportement d'exception ne contredit-il pas les documents ici ( docs.microsoft.com/en-us/dotnet/api/… ) "Si l'une des tâches fournies se termine dans un état défectueux, la tâche renvoyée se terminera également dans un état défaillant. , où ses exceptions contiendront l'agrégation de l'ensemble des exceptions non encapsulées de chacune des tâches fournies. "
Dasith Wijes
1
Je considère que c'est un artefact await, pas une différence entre les deux méthodes. Les deux propagent un AggregateException, en lançant directement ou via une propriété (la Task.Exceptionpropriété).
Theodor Zoulias
20

Comme exemple de différence - si vous avez une tâche, elle fait quelque chose avec le thread d'interface utilisateur (par exemple, une tâche qui représente une animation dans un Storyboard) si vous Task.WaitAll()le thread d'interface utilisateur est alors bloqué et l'interface utilisateur n'est jamais mise à jour. si vous utilisez, await Task.WhenAll()le thread d'interface utilisateur n'est pas bloqué et l'interface utilisateur sera mise à jour.

J. Long
la source
7

Que font-ils:

  • En interne, les deux font la même chose.

Quelle est la différence:

  • WaitAll est un appel bloquant
  • WhenAll - not - le code continuera à s'exécuter

À utiliser lorsque:

  • WaitAll quand ne peut pas continuer sans avoir le résultat
  • Quand Tout ce qui vient d'être notifié, pas bloqué
i100
la source
1
@MartinRhodes Mais que se passe-t-il si vous ne l'attendez pas immédiatement, mais continuez avec d'autres travaux, puis attendez-le? Vous n'avez pas cette possibilité avec ce WaitAllque je comprends.
Jeppe
@Jeppe Ne différeriez-vous pas simplement l'appel après Task.WaitAll avoir fait votre autre travail? Je veux dire, au lieu de l'appeler juste après avoir commencé vos tâches.
PL