Как можно дождаться выполнения всех задач из массива задач (Task[]) в C#?

Ответ

Для ожидания завершения группы задач используйте статические методы Task.WaitAll (блокирующий) или Task.WhenAll (асинхронный).

1. Task.WaitAll(Task[]) — синхронное (блокирующее) ожидание. Останавливает текущий поток до завершения всех задач. Используйте в синхронном коде, но избегайте в UI-потоках или асинхронных методах, чтобы не вызывать deadlock.

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

// Текущий поток блокируется здесь
Task.WaitAll(tasks);
Console.WriteLine("Все задачи завершены.");

2. await Task.WhenAll(Task[]) — асинхронное ожидание. Предпочтительный способ в асинхронных методах (async). Не блокирует поток, а освобождает его для других операций.

public async Task ProcessAllDataAsync()
{
    Task<int>[] downloadTasks = new Task<int>[5];
    for (int i = 0; i < downloadTasks.Length; i++)
    {
        downloadTasks[i] = DownloadDataAsync(i);
    }

    // Ожидаем завершения всех задач, не блокируя поток
    int[] results = await Task.WhenAll(downloadTasks);
    // Когда все задачи завершены, работаем с результатами
    int sum = results.Sum();
    Console.WriteLine($"Общая сумма: {sum}");
}

Критически важная деталь: Обработка исключений.

  • Task.WaitAll и await Task.WhenAll при ошибке в любой из задач выбрасывают AggregateException (в .NET Core 3.0+ WaitAll может выбросить первое исключение).
  • Правильный подход — проверять статус каждой задачи:
    
    try
    {
    await Task.WhenAll(tasks);
    }
    catch // Ловим первое исключение, но AggregateException могло быть "развернуто"
    {
    // Игнорируем здесь, проверяем задачи ниже
    }

// Анализируем каждую задачу на наличие ошибок foreach (var task in tasks) { if (task.IsFaulted) { // task.Exception - это AggregateException, содержащее реальные ошибки Console.WriteLine($"Задача завершилась с ошибкой: {task.Exception?.InnerException?.Message}"); } }


Используйте `WhenAll` для асинхронного кода и всегда планируйте обработку частичных сбоев.

Ответ 18+ 🔞

Смотри, есть два главных способа дождаться, пока куча твоих тасков сделают свои дела. Как будто ты отправил кучу курьеров и ждёшь, пока все вернутся, чтобы посчитать бабки.

Первый — Task.WaitAll(Task[]). Это как посадить себя на цепь. Пока все задачи не приползут, твой текущий поток встанет как вкопанный. Никуда не денется. Используй это в синхронном коде, но, блядь, ни в коем случае не лезь с этим в UI-поток или в async метод — там тебя просто заблокирует насмерть, и будет тебе вечный deadlock, ёпта.

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

// Всё, тут поток упрётся лбом в стенку и будет ждать
Task.WaitAll(tasks);
Console.WriteLine("Все задачи завершены.");

Второй — await Task.WhenAll(Task[]). Это по-умному. Вот это уже красота. Кинул задачи, сказал "ждитесь", а сам поток освободил — пусть другие дела делает. Используется внутри async методов.

public async Task ProcessAllDataAsync()
{
    Task<int>[] downloadTasks = new Task<int>[5];
    for (int i = 0; i < downloadTasks.Length; i++)
    {
        downloadTasks[i] = DownloadDataAsync(i);
    }

    // Спокойно ждём, не блокируя весь мир вокруг
    int[] results = await Task.WhenAll(downloadTasks);
    // Все приползли, можно результаты собирать
    int sum = results.Sum();
    Console.WriteLine($"Общая сумма: {sum}");
}

А теперь, сука, самое важное — исключения. И WaitAll, и WhenAll при первой же ошибке в любой задаче выкинут тебе исключение. Но проблема в том, что если одна задача наебнулась, а остальные норм, то тебе надо понять, кто именно сломался и почему.

  • Просто поймать try/catch — это полдела. Там тебе, возможно, прилетит AggregateException (или в новых .NET Core его могут развернуть).
  • Правильный, полный подход — проверить каждую задачу по отдельности, как подозреваемого:
try
{
    await Task.WhenAll(tasks);
}
catch // Ловим что прилетело, но это может быть только первая ошибка
{
    // А нам похуй на этот catch сейчас, мы пойдём ниже смотреть каждую задачу
}

// Вот теперь идём и выясняем, кто тут у нас мудак
foreach (var task in tasks)
{
    if (task.IsFaulted) // Эта задача накосячила
    {
        // Залезаем в task.Exception (это опять AggregateException) и смотрим корень зла
        Console.WriteLine($"Задача обосралась: {task.Exception?.InnerException?.Message}");
    }
}

Короче, запомни: для асинхронщины — WhenAll, и всегда проверяй задачи по одной на предмет косяков, потому что жизнь не идеальна и что-то всегда может пойти не так.