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

«В чем разница между Task.WaitAll и Task.WhenAll в C#?» — вопрос из категории Многопоточность, который задают на 25% собеседований 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 консольного приложения.