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

Как можно дождаться выполнения всех задач из массива задач?

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

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

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

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

Ожидание выполнения всех задач в C#

В C# для ожидания выполнения всех задач из массива или коллекции существует несколько эффективных подходов. Выбор конкретного метода зависит от версии .NET и требуемой логики обработки.

Основные методы ожидания

1. Task.WaitAll() (синхронное ожидание)

Блокирует текущий поток до завершения всех задач. Этот подход следует использовать с осторожностью, так как он может привести к взаимоблокировкам в UI-приложениях.

Task[] tasks = new Task[3]
{
    Task.Run(() => DoWork(1)),
    Task.Run(() => DoWork(2)),
    Task.Run(() => DoWork(3))
};

// Блокирующее ожидание всех задач
Task.WaitAll(tasks);
Console.WriteLine("Все задачи завершены");

2. Task.WhenAll() (асинхронное ожидание)

Создает задачу, которая завершается после выполнения всех предоставленных задач. Это предпочтительный подход в асинхронном программировании.

async Task ProcessAllTasksAsync()
{
    Task[] tasks = new Task[3]
    {
        DoWorkAsync(1),
        DoWorkAsync(2),
        DoWorkAsync(3)
    };
    
    // Асинхронное ожидание без блокировки потока
    await Task.WhenAll(tasks);
    Console.WriteLine("Все задачи завершены");
}

3. Task.WhenAll() с возвращаемыми значениями

Если задачи возвращают результаты, можно использовать типизированную версию:

async Task ProcessWithResultsAsync()
{
    Task<int>[] tasks = new Task<int>[]
    {
        GetResultAsync(1),
        GetResultAsync(2),
        GetResultAsync(3)
    };
    
    // Ожидаем завершения всех задач и получаем результаты
    int[] results = await Task.WhenAll(tasks);
    Console.WriteLine($"Сумма результатов: {results.Sum()}");
}

Дополнительные возможности

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

При использовании Task.WhenAll() исключения из всех задач агрегируются в AggregateException:

async Task HandleExceptionsAsync()
{
    Task[] tasks = new Task[]
    {
        Task.Run(() => { throw new InvalidOperationException("Ошибка 1"); }),
        Task.Run(() => { throw new ArgumentException("Ошибка 2"); })
    };
    
    try
    {
        await Task.WhenAll(tasks);
    }
    catch (AggregateException ae)
    {
        // Обработка всех исключений
        foreach (var ex in ae.InnerExceptions)
        {
            Console.WriteLine($"Исключение: {ex.Message}");
        }
    }
}

Ожидание с таймаутом

async Task WaitWithTimeoutAsync()
{
    Task[] tasks = new Task[]
    {
        DoLongWorkAsync(1),
        DoLongWorkAsync(2)
    };
    
    // Создаем задачу таймаута
    Task timeoutTask = Task.Delay(5000);
    
    // Ожидаем завершения либо всех задач, либо таймаута
    Task completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);
    
    if (completedTask == timeoutTask)
    {
        Console.WriteLine("Превышено время ожидания");
        // Можно отменить задачи через CancellationToken
    }
}

Использование с CancellationToken

async Task ProcessWithCancellationAsync()
{
    using var cts = new CancellationTokenSource();
    cts.CancelAfter(3000); // Автоотмена через 3 секунды
    
    var tasks = new List<Task>();
    
    for (int i = 0; i < 5; i++)
    {
        tasks.Add(DoWorkWithCancellationAsync(i, cts.Token));
    }
    
    try
    {
        await Task.WhenAll(tasks);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Операция отменена");
    }
}

Рекомендации по использованию

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

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

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

  • В синхронном коде, где нельзя использовать async/await
  • В тестовых методах или консольных приложениях
  • При необходимости гарантировать завершение задач перед продолжением

Производительность и ограничения:

  • Task.WhenAll() создает новую задачу-обертку
  • При большом количестве задач (тысячи) рассмотрите использование Parallel.ForEach или пакетной обработки
  • Для динамически добавляемых задач используйте List<Task> с последующим вызовом Task.WhenAll()

Важное замечание: Всегда обрабатывайте исключения при ожидании нескольких задач, так как одна неудачная задача не должна препятствовать обработке исключений из других задач.

Выбор между Task.WaitAll() и Task.WhenAll() в основном зависит от контекста выполнения: синхронный код требует блокирующего ожидания, в то время как асинхронные сценарии выигрывают от неблокирующего подхода.