WaitAll vs WhenAll

335

Qual é a diferença entre Task.WaitAll()e Task.WhenAll()do CTP assíncrono? Você pode fornecer algum código de exemplo para ilustrar os diferentes casos de uso?

Yaron Levi
fonte

Respostas:

504

Task.WaitAll bloqueia o encadeamento atual até que tudo esteja completo.

Task.WhenAllretorna uma tarefa que representa a ação de aguardar até que tudo esteja concluído.

Isso significa que, a partir de um método assíncrono, você pode usar:

await Task.WhenAll(tasks);

... o que significa que seu método continuará quando tudo estiver concluído, mas você não amarrará um fio para ficar por aqui até esse momento.

Jon Skeet
fonte
2
Depois de muita leitura, é claro que assíncrona não tem nada a ver com tópicos blog.stephencleary.com/2013/11/there-is-no-thread.html
Vince Panuccio
7
@Vince: Eu acho que "nada a ver com threads" é um exagero, e é importante entender como as operações assíncronas interagem com os threads.
Jon Skeet
6
@KevinBui: Não, não deve bloqueá- lo - ele aguardará a tarefa retornada WhenAll, mas isso não é o mesmo que bloquear o encadeamento.
Jon tiro ao prato
1
@ JonSkeet Talvez a distinção precisa entre esses dois seja sutil demais para mim. Você pode me apontar (e possivelmente o resto de nós) alguma referência que deixará clara a diferença?
CatShoes
125
@ CatShoes: Na verdade não - eu expliquei isso da melhor maneira possível. Eu acho que eu poderia dar uma analogia - é como a diferença entre pedir um take-away e ficar de pé junto à porta esperando que ele chegue, vs pedir um take-away, fazer outras coisas e abrir a porta quando o correio chegar ...
Jon Skeet
51

Enquanto a resposta de JonSkeet explica a diferença de uma maneira tipicamente excelente, há outra diferença: tratamento de exceções .

Task.WaitAlllança um AggregateExceptionquando qualquer uma das tarefas é lançada e você pode examinar todas as exceções lançadas. O awaitin await Task.WhenAlldesembrulha o AggregateExceptione 'retorna' apenas a primeira exceção.

Quando o programa abaixo é executado com await Task.WhenAll(taskArray)a saída é o seguinte.

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.

Quando o programa abaixo é executado com Task.WaitAll(taskArray)a saída é o seguinte.

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.

O programa:

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();
    }
}
timtim
fonte
13
a maior diferença prática é o tratamento de exceções. Realmente? Porque essa realmente não é a maior diferença prática. A maior diferença prática é que um é assíncrono e não bloqueador, enquanto o outro está bloqueando. Isso é muito mais importante do que como ele lida com exceções.
Liam
5
Obrigado por apontar isso. Essa explicação foi útil no cenário em que estou trabalhando atualmente. Talvez não seja a "maior diferença prática", mas definitivamente uma boa opção.
Urk
A manipulação de exceção sendo maior diferença prática pode ser mais aplicável à comparação entre await t1; await t2; await t3;vsawait Task.WhenAll(t1,t2,t3);
frostshoxx
1
Esse comportamento de exceção não contradiz os documentos aqui ( docs.microsoft.com/en-us/dotnet/api/… ) "Se alguma das tarefas fornecidas for concluída em um estado com falha, a tarefa retornada também será concluída em um estado com falha , em que suas exceções conterão a agregação do conjunto de exceções não empacotadas de cada uma das tarefas fornecidas ".
Dasith Wijes
1
Considero que este é um artefato await, não uma diferença entre os dois métodos. Ambos propagam um AggregateException, jogando diretamente ou através de uma propriedade (a Task.Exceptionpropriedade).
Theodor Zoulias
20

Como exemplo da diferença - se você tem uma tarefa, faz algo com o thread da interface do usuário (por exemplo, uma tarefa que representa uma animação em um Storyboard) se você Task.WaitAll()o thread da interface do usuário é bloqueado e a interface do usuário nunca é atualizada. se você usar await Task.WhenAll(), o thread da interface do usuário não será bloqueado e a interface do usuário será atualizada.

J. Long
fonte
7

O que eles fazem:

  • Internamente, ambos fazem a mesma coisa.

Qual é a diferença:

  • WaitAll é uma chamada de bloqueio
  • Quando todo o código - não - continuará executando

Use qual quando:

  • WaitAll quando não pode continuar sem ter o resultado
  • WhenAll quando o que apenas deve ser notificado, não bloqueado
i100
fonte
1
@MartinRhodes Mas e se você não esperam-lo imediatamente, mas continuar com algum outro trabalho e , em seguida, aguardar-lo? Você não tem essa possibilidade com o WaitAllque eu entendo.
Jeppe
@Jeppe Você não diferiria da ligação Task.WaitAll depois de fazer algum outro trabalho? Quero dizer, em vez de chamá-lo logo após iniciar suas tarefas.
PL