← Назад к вопросам

В чем разница между WaitAll и WhenAll?

2.2 Middle🔥 141 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Различие между Task.WaitAll и Task.WhenAll в C#

Task.WaitAll и Task.WhenAll - это два метода из пространства имен System.Threading.Tasks, которые служат для ожидания завершения нескольких задач, но имеют фундаментально разные подходы к выполнению этой операции.

Основное концептуальное отличие

Task.WaitAll - это блокирующий синхронный метод, который приостанавливает выполнение текущего потока до завершения всех переданных задач.

Task.WhenAll - это неблокирующий асинхронный метод, который возвращает новую задачу (Task или Task<TResult>), завершающуюся тогда, когда завершатся все переданные задачи.

Детальное сравнение

Task.WaitAll - блокирующее ожидание

async Task ExampleWaitAll()
{
    Task task1 = Task.Delay(1000);
    Task task2 = Task.Delay(2000);
    Task task3 = Task.Delay(1500);
    
    // Текущий поток БЛОКИРУЕТСЯ на ~2 секунды (макс. время выполнения)
    Task.WaitAll(task1, task2, task3);
    
    Console.WriteLine("Все задачи завершены (блокирующий вызов)");
}

Характеристики WaitAll:

  • Блокирует текущий поток до завершения всех задач
  • Может вызывать взаимоблокировки (deadlocks) в UI-потоках или контекстах синхронизации
  • Выбрасывает AggregateException, если любая из задач завершилась с ошибкой
  • Не может использоваться с ключевым словом await
  • Часто используется в консольных приложениях или фоновых службах

Task.WhenAll - неблокирующее ожидание

async Task ExampleWhenAll()
{
    Task task1 = Task.Delay(1000);
    Task task2 = Task.Delay(2000);
    Task task3 = Task.Delay(1500);
    
    // Возвращает задачу, которая завершится, когда все задачи завершатся
    Task allTasks = Task.WhenAll(task1, task2, task3);
    
    // Неблокирующее ожидание с освобождением текущего потока
    await allTasks;
    
    Console.WriteLine("Все задачи завершены (неблокирующий вызов)");
}

Характеристики WhenAll:

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

Ключевые отличия в использовании

Обработка исключений

// WaitAll - выбрасывает AggregateException
try
{
    Task.WaitAll(Task.FromException(new InvalidOperationException()));
}
catch (AggregateException ex)
{
    foreach (var innerEx in ex.InnerExceptions)
    {
        Console.WriteLine(innerEx.Message);
    }
}

// WhenAll - исключения выбрасываются при await
try
{
    await Task.WhenAll(Task.FromException(new InvalidOperationException()));
}
catch (InvalidOperationException ex)  // Не AggregateException!
{
    Console.WriteLine(ex.Message);
}

Важно: Task.WhenAll "разворачивает" AggregateException и выбрасывает первое исключение из коллекции при использовании await.

Возвращаемые значения

// WhenAll с возвращаемыми значениями
async Task ExampleWhenAllWithResults()
{
    Task<int> task1 = Task.FromResult(10);
    Task<int> task2 = Task.FromResult(20);
    Task<int> task3 = Task.FromResult(30);
    
    // Возвращает Task<int[]>
    int[] results = await Task.WhenAll(task1, task2, task3);
    // results = [10, 20, 30]
    
    Console.WriteLine($"Сумма: {results.Sum()}");
}

// WaitAll не возвращает значений
void ExampleWaitAllWithResults()
{
    Task<int> task1 = Task.FromResult(10);
    Task<int> task2 = Task.FromResult(20);
    
    Task.WaitAll(task1, task2);
    // Чтобы получить результаты, нужно обращаться к каждой задаче отдельно
    int result1 = task1.Result;
    int result2 = task2.Result;
}

Практические рекомендации

Когда использовать Task.WhenAll:

  • В асинхронных методах с async/await
  • В веб-приложениях (ASP.NET Core, Web API)
  • В UI-приложениях (WPF, WinForms, Blazor)
  • Когда нужно выполнить несколько независимых операций параллельно

Когда использовать Task.WaitAll:

  • В синхронных методах, где нельзя использовать async
  • В точках входа консольных приложений (Main метод)
  • В legacy-коде или при миграции с более старых версий .NET
  • В тестовых методах без поддержки async/await

Пример реального использования

public class DataService
{
    public async Task<IEnumerable<Data>> LoadAllDataAsync()
    {
        // Параллельная загрузка данных из разных источников
        Task<IEnumerable<User>> usersTask = LoadUsersAsync();
        Task<IEnumerable<Product>> productsTask = LoadProductsAsync();
        Task<IEnumerable<Order>> ordersTask = LoadOrdersAsync();
        
        // Неблокирующее ожидание всех задач
        await Task.WhenAll(usersTask, productsTask, ordersTask);
        
        // Комбинирование результатов
        return CombineData(
            await usersTask, 
            await productsTask, 
            await ordersTask
        );
    }
    
    // Антипаттерн - блокировка в асинхронном контексте
    public IEnumerable<Data> LoadAllDataBlocking()
    {
        Task<IEnumerable<User>> usersTask = LoadUsersAsync();
        Task<IEnumerable<Product>> productsTask = LoadProductsAsync();
        
        // ПЛОХО: блокировка асинхронных операций
        Task.WaitAll(usersTask, productsTask);
        
        // Риск взаимоблокировок и неэффективного использования потоков
        return CombineData(usersTask.Result, productsTask.Result);
    }
}

Производительность и масштабируемость

Task.WhenAll обеспечивает лучшую масштабируемость, поскольку:

  • Не занимает поток во время ожидания
  • Позволяет выполнять другие операции в том же потоке
  • Меньше нагружает пул потоков ThreadPool
  • Лучше работает под высокой нагрузкой

Task.WaitAll может быть причиной:

  • Взаимоблокировок (особенно с ConfigureAwait(false))
  • Исчерпания пула потоков
  • Ухудшения отзывчивости UI
  • Снижения общей производительности системы

Заключение

В современных приложениях на C# Task.WhenAll является предпочтительным выбором в 95% случаев благодаря своей неблокирующей природе и интеграции с async/await паттерном. Task.WaitAll следует использовать осознанно, только в специфических сценариях, где синхронное поведение действительно необходимо, понимая риски связанные с блокировками потоков и потенциальными взаимоблокировками.

В чем разница между WaitAll и WhenAll? | PrepBro