WaitAll vs WhenAll

335

Jaka jest różnica pomiędzy Task.WaitAll()i Task.WhenAll()od Async CTP? Czy możesz podać przykładowy kod ilustrujący różne przypadki użycia?

Yaron Levi
źródło

Odpowiedzi:

504

Task.WaitAll blokuje bieżący wątek, dopóki wszystko się nie zakończy.

Task.WhenAllzwraca zadanie reprezentujące akcję czekania, aż wszystko się zakończy.

Oznacza to, że z metody asynchronicznej możesz użyć:

await Task.WhenAll(tasks);

... co oznacza, że ​​twoja metoda będzie kontynuowana, gdy wszystko się skończy, ale do tego czasu nie będziesz wiązać nici, żeby się po prostu zawiesić.

Jon Skeet
źródło
2
Po długim
Vince Panuccio
7
@Vince: Myślę, że „nic wspólnego z wątkami” jest zawyżeniem i ważne jest, aby zrozumieć, w jaki sposób operacje asynchroniczne współdziałają z wątkami.
Jon Skeet
6
@KevinBui: Nie, nie powinien go blokować - będzie oczekiwał na zadanie zwrócone przez WhenAll, ale to nie to samo, co blokowanie wątku.
Jon Skeet,
1
@JonSkeet Być może dokładne rozróżnienie między tymi dwoma jest dla mnie zbyt subtelne. Czy możesz wskazać mi (i ewentualnie reszcie z nas) jakieś odniesienie, które wyjaśni różnicę?
CatShoes,
125
@CatShoes: Nie bardzo - wyjaśniłem to tak dobrze, jak potrafię. Wydaje mi się, że mógłbym podać analogię - to jak różnica między zamówieniem dania na wynos a stojeniem przy drzwiach i czekaniem, aż nadejdzie, w porównaniu z zamówieniem dania na wynos, robieniem innych rzeczy, a następnie otwarciem drzwi, kiedy przyjedzie kurier ...
Jon Skeet,
51

Podczas gdy odpowiedź JonSkeeta wyjaśnia różnicę w typowo doskonały sposób, istnieje jeszcze jedna różnica: obsługa wyjątków .

Task.WaitAllrzuca AggregateExceptionkiedy rzuca dowolne z zadań i możesz sprawdzić wszystkie zgłoszone wyjątki. Opcja awaitin await Task.WhenAllrozpakowuje AggregateExceptioni „zwraca” tylko pierwszy wyjątek.

Gdy program poniżej zostanie wykonany z await Task.WhenAll(taskArray)danymi wyjściowymi, jest on następujący.

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.

Gdy poniższy program jest wykonywany z Task.WaitAll(taskArray)danymi wyjściowymi, jest on następujący.

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.

Program:

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
źródło
13
największą praktyczną różnicą jest obsługa wyjątków. Naprawdę? Ponieważ to naprawdę nie jest największa praktyczna różnica. Największą praktyczną różnicą jest to, że jedna jest asynchroniczna i nie blokuje, podczas gdy druga blokuje. Jest to o wiele ważniejsze niż sposób obsługi wyjątków.
Liam
5
Dzięki za zwrócenie na to uwagi. To wyjaśnienie było przydatne w scenariuszu, nad którym obecnie pracuję. Być może nie „największa praktyczna różnica”, ale zdecydowanie dobre wezwanie.
Urk
Obsługa wyjątków, będąca największą praktyczną różnicą, może mieć większe zastosowanie do porównania await t1; await t2; await t3;vsawait Task.WhenAll(t1,t2,t3);
frostshoxx
1
Czy to zachowanie wyjątkowe nie jest sprzeczne z dokumentacją tutaj ( docs.microsoft.com/en-us/dotnet/api/... ) „Jeśli którekolwiek z dostarczonych zadań zostaną zakończone w stanie błędu , zwrócone zadanie zostanie również zakończone w stanie błędu , gdzie wyjątki będą zawierać agregację zbioru nieopakowanych wyjątków z każdego dostarczonego zadania. ”
Dasith Wijes
1
Uważam to za artefakt await, a nie różnicę między tymi dwiema metodami. Obie propagują AggregateException, rzucając bezpośrednio lub przez właściwość ( Task.Exceptionwłaściwość).
Theodor Zoulias
20

Jako przykład różnicy - jeśli masz zadanie, robi coś z wątkiem interfejsu użytkownika (np. Zadanie reprezentujące animację w serii ujęć), jeśli Task.WaitAll()następnie wątek interfejsu jest zablokowany, a interfejs użytkownika nigdy nie jest aktualizowany. jeśli używasz, await Task.WhenAll()to wątek interfejsu użytkownika nie jest blokowany, a interfejs użytkownika zostanie zaktualizowany.

J. Long
źródło
7

Co oni robią:

  • Wewnętrznie obaj robią to samo.

Co za różnica:

  • WaitAll to połączenie blokujące
  • WhenAll -not-code będzie kontynuował wykonywanie

Użyj, gdy:

  • Poczekaj, gdy nie będzie można kontynuować bez uzyskania wyniku
  • Kiedy Wszystko , co tylko należy powiadomić, nie jest zablokowane
i100
źródło
1
@MartinRhodes ale co jeśli nie czekają go natychmiast, ale nadal z innej pracy, a następnie czekać na niego? Nie masz takiej możliwości, WaitAlljak rozumiem.
Jeppe
@Jeppe Czy nie zmieniłbyś swojego połączenia Task.WaitAll po wykonaniu innej pracy? Mam na myśli, zamiast nazywać go zaraz po rozpoczęciu zadań.
PL