8.4. Gather¶
У попередньому розділі показано, як asyncio.create_task() планує корутину і не чекає на неї. Супутня операція – запустити кілька awaitable одночасно і чекати на всі – це asyncio.gather().
8.4.1. Базова форма¶
import asyncio
async def fetch(name, delay_ms):
await asyncio.sleep_ms(delay_ms)
return name
async def main():
results = await asyncio.gather(
fetch("a", 100),
fetch("b", 200),
fetch("c", 50),
)
print(results)
asyncio.run(main())
Output:
['a', 'b', 'c']
Два важливих спостереження. По-перше, список результатів упорядкований відповідно до аргументів gather, а не у порядку завершення корутин – "c" завершилась першою, але все одно є третім записом. По-друге, виклик тривав 200 ms загалом, а не 350 ms: три сни виконувалися одночасно, і gather() повертається, щойно завершується найповільніший.
Обидва факти мають одне джерело. gather() обгортає кожен аргумент, який ще не є задачею (корутини в прикладі), планує їх у циклі, призупиняє корутину, що викликала, до завершення всіх з них, а потім повертає їхні результати в початковому порядку.
8.4.2. Коли варто використовувати¶
Скрізь, де програма має N awaitable операцій і хоче отримати результати всіх перед продовженням. Типові приклади:
Паралельне виконання кількох мережевих запитів і очікування всіх відповідей.
Одночасне зчитування з кількох датчиків перед обробкою об’єднаного результату.
Об’єднання кількох короткочасних допоміжних задач наприкінці фази програми.
Це не правильний інструмент для довготривалих фонових задач, які програма запускає та залишає працювати протягом усього часу – для них як і раніше використовується create_task(). gather() призначений для шаблону розгалуження / злиття: розділити роботу, виконати паралельно, об’єднати.
8.4.3. Винятки в групі¶
Якщо будь-який з gathered awaitable генерує виняток, стандартна поведінка – повторно генерувати виняток за межі виклику gather. Сусідні задачі, що ще не завершилися, скасовуються у фоні.
Зазвичай це те, чого хоче програма – одна з N паралельних задач зазнала невдачі, тому спільна операція зазнала невдачі, тому не витрачайте час на решту. Іноді програма хоче протилежного: дозволити кожному awaitable завершитися (або зазнати невдачі) незалежно, а потім перевірити результати. Для цього передайте return_exceptions=True
results = await asyncio.gather(
fetch_or_fail("ok"),
fetch_or_fail("bad"),
return_exceptions=True,
)
# results == ["ok-value", OSError(...)]
Тепер кожен запис у поверненому списку є або звичайним поверненим значенням, або винятком, який згенерував відповідний awaitable. Виклик перевіряє isinstance(r, Exception), щоб розрізнити їх.
8.4.4. Скасування¶
Скасування самого gather – через скасування будь-якої задачі, яка його очікувала – скасовує кожен awaitable, що ще виконується всередині нього. Сторінка тайм-аути та скасування детально описує, як скасування поширюється по ланцюжку викликів.