WaitAll vs WhenAll

Respuestas:

504

Task.WaitAll bloquea el hilo actual hasta que todo se haya completado.

Task.WhenAlldevuelve una tarea que representa la acción de esperar hasta que todo se haya completado.

Eso significa que desde un método asíncrono, puede usar:

await Task.WhenAll(tasks);

... lo que significa que su método continuará cuando todo esté completo, pero no atará un hilo para quedarse hasta ese momento.

Jon Skeet
fuente
2
Después de mucha lectura, está claro que async no tiene nada que ver con los hilos blog.stephencleary.com/2013/11/there-is-no-thread.html
Vince Panuccio
77
@Vince: Creo que "nada que ver con hilos" es una exageración, y es importante entender cómo las operaciones asíncronas interactúan con hilos.
Jon Skeet
66
@KevinBui: No, no debería bloquearlo ; esperará la tarea devuelta WhenAll, pero eso no es lo mismo que bloquear el hilo.
Jon Skeet
1
@ JonSkeet Quizás la distinción precisa entre esos dos es demasiado sutil para mí. ¿Puede señalarme (y posiblemente al resto de nosotros) alguna referencia que aclare la diferencia?
CatShoes
125
@ CatShoes: No realmente, ya lo he explicado tan bien como puedo. Supongo que podría dar una analogía: es como la diferencia entre pedir una comida para llevar y estar de pie junto a la puerta esperando que llegue, frente a pedir una comida para llevar, hacer otras cosas y luego abrir la puerta cuando llega el servicio de mensajería ...
Jon Skeet
51

Si bien la respuesta de JonSkeet explica la diferencia de una manera típicamente excelente, hay otra diferencia: el manejo de excepciones .

Task.WaitAlllanza un AggregateExceptioncuando se lanza cualquiera de las tareas y puede examinar todas las excepciones lanzadas. El awaiten await Task.WhenAlldesenvuelve los AggregateExceptiony 'devuelve' sólo la primera excepción.

Cuando el siguiente programa se ejecuta con await Task.WhenAll(taskArray)la salida es el siguiente.

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.

Cuando el siguiente programa se ejecuta con Task.WaitAll(taskArray)la salida es la siguiente.

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.

El 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();
    }
}
tymtam
fuente
13
La mayor diferencia práctica es el manejo de excepciones. De Verdad? Porque eso realmente no es la mayor diferencia práctica. La mayor diferencia práctica es que uno es asíncrono y sin bloqueo, mientras que el otro es el bloqueo. Esto es mucho más importante que cómo maneja las excepciones.
Liam
55
Gracias por señalar esto. Esta explicación fue útil en el escenario en el que estoy trabajando actualmente. Quizás no sea la "mayor diferencia práctica", pero definitivamente es una buena llamada.
Urk
El manejo de ser el principal diferencia práctica excepción podría ser más aplicable a la comparación entre await t1; await t2; await t3;vsawait Task.WhenAll(t1,t2,t3);
frostshoxx
1
¿Este comportamiento de excepción no contradice los documentos aquí ( docs.microsoft.com/en-us/dotnet/api/… ) "Si alguna de las tareas suministradas se completa en un estado anómalo, la tarea devuelta también se completará en un estado Anómalo , donde sus excepciones contendrán la agregación del conjunto de excepciones sin envolver de cada una de las tareas proporcionadas ".
Dasith Wijes
1
Considero que esto es un artefacto await, no una diferencia entre los dos métodos. Ambos propagan un AggregateException, lanzando directamente o a través de una propiedad (la Task.Exceptionpropiedad).
Theodor Zoulias
20

Como ejemplo de la diferencia: si tiene una tarea, hace algo con el hilo de la interfaz de usuario (por ejemplo, una tarea que representa una animación en un guión gráfico) si Task.WaitAll()luego el hilo de la interfaz de usuario está bloqueado y la interfaz de usuario nunca se actualiza. si lo usa await Task.WhenAll(), el hilo de la interfaz de usuario no está bloqueado y la interfaz de usuario se actualizará.

J. Long
fuente
7

Qué hacen:

  • Internamente, ambos hacen lo mismo.

Cual es la diferencia:

  • WaitAll es una llamada de bloqueo
  • Cuando todo - no - el código continuará ejecutándose

Use cual cuando:

  • WaitAll cuando no puede continuar sin tener el resultado
  • Cuándo Todo cuándo qué solo para ser notificado, no bloqueado
i100
fuente
1
@MartinRhodes Pero, ¿qué pasa si no lo espera de inmediato, sino que continúa con algún otro trabajo y luego lo espera? No tienes esa posibilidad con lo WaitAllque yo entiendo.
Jeppe
@Jeppe ¿No diferirías de la llamada Task.WaitAll después de que hiciste tu otro trabajo? Quiero decir, en lugar de llamarlo justo después de comenzar tus tareas.
PL