OOP Course
Сьогодні

Підрозділ 17.3

Послідовне та паралельне виконання. Task.WhenAll та Task.WhenAny

17.3. Послідовне та паралельне виконання. Task.WhenAll та Task.WhenAny У реальних асинхронних системах рідко виникає потреба виконати лише одну операцію. Зазвичай потрібно координувати кілька: завантажити кільк

17.3. Послідовне та паралельне виконання. Task.WhenAll та Task.WhenAny

У реальних асинхронних системах рідко виникає потреба виконати лише одну операцію. Зазвичай потрібно координувати кілька: завантажити кілька ресурсів, дочекатися першого результату з набору джерел, або збудувати складний конвеєр обробки. Для цього C# надає два потужні комбінатори: Task.WhenAll і Task.WhenAny.

Послідовне vs паралельне виконання в async

Послідовне виконання — коли порядок має значення

Якщо кожна наступна операція залежить від результату попередньої, виконання мусить бути послідовним. await кожного кроку по черзі — природній спосіб висловити цю залежність:

Послідовне виконання — правильний вибір, коли операції пов'язані ланцюгом залежностей. Загальний час дорівнює сумі часів усіх кроків.

Паралельне виконання — коли операції незалежні

Якщо кілька операцій не залежать одна від одної, їх можна запустити одночасно. Ключова ідея: не await кожну операцію одразу — спочатку запусти всі, потім очікуй всіх:

Загальний час паралельного виконання — час найдовшої операції (~400 мс замість ~950 мс). Звісно, це справедливо тільки для справді незалежних операцій.

Task.WhenAll — очікування всіх завдань

Task.WhenAll — найчистіший спосіб запустити кілька завдань паралельно і дочекатися їх усіх. Він приймає колекцію Task або масив Task і повертає Task, що завершується, коли завершаться всі передані завдання:

Task.WhenAll з масивом однотипних завдань

Якщо всі завдання мають однаковий тип Task<T>, WhenAll повертає Task<T[]> — масив результатів у порядку вихідних завдань:

Обробка помилок у Task.WhenAll

Task.WhenAll дочікується всіх завдань, навіть якщо деякі завершились з помилкою. Після завершення він кидає AggregateException, що містить всі помилки від усіх завдань, що завершились невдало:

Важлива деталь: await Task.WhenAll(...) при помилці розгортає лише першу з виключень. Щоб отримати всі помилки, потрібно аналізувати task.Exception.InnerExceptions для кожного завдання після завершення WhenAll.

Task.WhenAny — очікування першого завдання

Task.WhenAny завершується, щойно перше з переданих завдань завершиться (успішно або з помилкою). Це корисно для сценаріїв: «отримати відповідь від найшвидшого сервера», «реалізувати тайм-аут», «обробляти результати по мірі готовності»:

Тайм-аут через Task.WhenAny + Task.Delay

Один з найпоширеніших патернів з WhenAny — реалізація тайм-ауту для асинхронної операції:

Зверніть: Task.WhenAny не скасовує інші завдання — SlowDatabaseQueryAsync продовжить виконуватись у фоні навіть після тайм-ауту. Щоб коректно скасувати, потрібно CancellationToken (розглядається у розділі 17.5).

Обробка результатів по мірі готовності

WhenAny дозволяє обробляти результати одразу, як вони з'являються, без очікування всіх:

Цей патерн дозволяє одразу реагувати на перші результати, не чекаючи найповільніших — важливо для інтерактивних систем.

Порівняння: WhenAll vs WhenAny

Аспект Task.WhenAll Task.WhenAny
Завершується коли Всі задачі завершились Перша задача завершилась
Результат T[] (для Task<T>[]) Перша завершена Task<T>
Помилки Збирає всі, кидає AggregateException Тільки від першої завершеної
Типові сценарії Паралельне збирання даних Тайм-аут, перший відповів, обробка по черзі
Скасовує інші Ні Ні

Обидва методи є фундаментальними інструментами координації async-операцій. Їх комбінація дозволяє будувати складні стратегії паралельного виконання без явного управління потоками.

Розроблено Tomka Yurii · © 2026 ·