В чем разница между Task.WaitAll и Task.WhenAll в C#?

Ответ

Task.WaitAll и Task.WhenAll служат для ожидания завершения группы задач, но делают это принципиально разными способами, что влияет на поток выполнения и производительность.

Task.WaitAll(params Task[] tasks) — это синхронный блокирующий метод.

  • Вызывает блокировку текущего потока до тех пор, пока все переданные задачи не будут завершены (успешно, с ошибкой или отменой).
  • Вызов на UI-потоке приведет к зависанию интерфейса.
  • Не рекомендуется использовать в асинхронном коде (помеченном async), так как он блокирует поток.

Task.WhenAll(params Task[] tasks) — это асинхронный неблокирующий метод.

  • Возвращает новую задачу (Task), которая будет завершена, когда завершатся все исходные задачи.
  • Не блокирует текущий поток. Поток освобождается, пока задачи выполняются.
  • Предназначен для использования с оператором await внутри асинхронных методов.

Сравнение на практике:

public async Task ProcessDataAsync()
{
    Task<int> downloadTask = DownloadFileAsync();
    Task<string> apiTask = CallApiAsync();
    Task<bool> dbTask = UpdateDatabaseAsync();

    // ПЛОХО: Синхронная блокировка. Поток будет простаивать в ожидании.
    // Task.WaitAll(downloadTask, apiTask, dbTask);

    // ХОРОШО: Асинхронное ожидание. Поток освобождается для другой работы.
    await Task.WhenAll(downloadTask, apiTask, dbTask);

    // После WhenAll все задачи гарантированно завершены.
    int fileSize = downloadTask.Result;
    string response = apiTask.Result;
    bool success = dbTask.Result;
}

// Task.WhenAll также удобен для агрегации результатов.
public async Task<int[]> GetMultipleValuesAsync()
{
    Task<int> task1 = GetValueAsync(1);
    Task<int> task2 = GetValueAsync(2);
    Task<int> task3 = GetValueAsync(3);

    // Ожидаем завершения всех и сразу получаем массив результатов.
    int[] results = await Task.WhenAll(task1, task2, task3);
    return results; // [значение1, значение2, значение3]
}

Вывод: В современном асинхронном коде почти всегда следует использовать await Task.WhenAll(...). Task.WaitAll может быть оправдан только в контексте синхронного кода, например, в методе Main консольного приложения.

Ответ 18+ 🔞

Так, слушай, про эти ваши Task.WaitAll и Task.WhenAll — это ж классика, два способа наступить на одни и те же грабли, но с разным звуком.

Представь, что задачи — это твои кореша, которых ты послал за пивом. Task.WaitAll — это ты стоишь посреди коридора, упершись лбом в стену, и тупо ждешь, пока они все вернутся. Ни телевизор посмотреть, ни чипсы открыть — просто стоишь, блядь, как истукан, и поток твой намертво заблокирован. Вызови эту штуку в UI — и всё, интерфейс повиснет, пользователь будет материться, а ты будешь смотреть в пустой экран, как дурак.

А вот Task.WhenAll — это умный подход. Ты говоришь: «Ребята, как все будете с пивом — свистните». И сам идешь на диван, включаешь футбол, закусываешь чем есть. Поток-то твой освобождается! А когда все задачи доложат о выполнении, тебе просто прилетит уведомление. В коде это через await делается — красиво, асинхронно, без тормозов.

Смотри, как это в деле выглядит:

public async Task ProcessDataAsync()
{
    Task<int> downloadTask = DownloadFileAsync();
    Task<string> apiTask = CallApiAsync();
    Task<bool> dbTask = UpdateDatabaseAsync();

    // ТУПИКОВЫЙ ПУТЬ: Весь поток встанет колом.
    // Task.WaitAll(downloadTask, apiTask, dbTask);

    // ПУТЬ РАЗУМА: Ждем, не блокируя ничего.
    await Task.WhenAll(downloadTask, apiTask, dbTask);

    // Все уже готово, можно результаты забирать.
    int fileSize = downloadTask.Result;
    string response = apiTask.Result;
    bool success = dbTask.Result;
}

А ещё WhenAll — он ж не только ждет, он может результаты все разом собрать, красота:

public async Task<int[]> GetMultipleValuesAsync()
{
    Task<int> task1 = GetValueAsync(1);
    Task<int> task2 = GetValueAsync(2);
    Task<int> task3 = GetValueAsync(3);

    // Ждем всех и сразу получаем массив, без возни с каждым Result.
    int[] results = await Task.WhenAll(task1, task2, task3);
    return results; // [значение1, значение2, значение3]
}

Так что правило простое, ёпта: пишешь async метод — хочешь ждать несколько задач — берешь await Task.WhenAll(...) и спишь спокойно. Task.WaitAll оставь для каких-нибудь легаси-консольных приложений, где потоковому параличу всё равно не на кого влиять. Всё, вопрос закрыт.