Ответ
Для конкурентного запуска нескольких корутин и сбора их результатов используется высокоуровневая функция asyncio.gather().
Она принимает на вход одну или несколько корутин, планирует их выполнение в цикле событий и возвращает Future, который завершается, когда все переданные корутины отработали. Результатом является список возвращаемых значений корутин в том же порядке, в котором они были переданы.
Ключевые особенности:
- Простота использования: Идеально подходит для сценария "запустить всё и дождаться всех".
- Сбор результатов: Автоматически собирает и упорядочивает результаты.
- Обработка ошибок: По умолчанию, если одна из корутин вызывает исключение,
gather()немедленно прерывается и пробрасывает это исключение. Поведение можно изменить, установив флагreturn_exceptions=True, тогда исключения будут возвращены в списке результатов наравне с успешными значениями.
Пример:
import asyncio
async def fetch_data(source: str, delay: int) -> str:
print(f"Начинаю загрузку из {source}...")
await asyncio.sleep(delay)
result = f"Данные из {source}"
print(f"Загрузка из {source} завершена.")
return result
async def main():
# Запускаем обе корутины конкурентно
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("DB", 1)
)
print(f"nВсе результаты: {results}")
# Вывод покажет, что DB завершит работу раньше,
# но gather дождется завершения API.
# >> Начинаю загрузку из API...
# >> Начинаю загрузку из DB...
# >> Загрузка из DB завершена.
# >> Загрузка из API завершена.
# >>
# >> Все результаты: ['Данные из API', 'Данные из DB']
asyncio.run(main())
В отличие от asyncio.create_task(), которая только планирует выполнение корутины и немедленно возвращает объект Task, asyncio.gather() представляет собой более удобный способ для одновременного ожидания группы задач.
Ответ 18+ 🔞
А, слушай, смотри, есть же такая штука, чтобы кучу асинхронных дел разом запустить и не сойти с ума, пока ждёшь. Называется asyncio.gather(). Это типа как взять всех своих друзей-корутин и сказать: «Ну-ка, все нахуй, делайте своё дело, а я тут посижу, пока вы не закончите».
Вот в чём прикол:
- Просто до безобразия: Кинул туда список корутин — и забыл. Вернётся тебе аккуратный список результатов, в том же порядке, в котором ты их туда пихал. Красота, блядь.
- Собирает всё в кучу: Не нужно самому бегать и собирать результаты из каждой задачи — всё само приплывёт.
- С ошибками: Тут, конечно, есть нюанс. Если одна из этих корутин возьмёт и накосячит, выкинув исключение, то по умолчанию
gather()сразу же пизданутся и тебе эту ошибку в лицо бросит. Но если ты не идиот и хочешь получить всё, даже косяки, то поставь флагreturn_exceptions=True. Тогда исключения просто будут лежать в списке результатов, как ни в чём не бывало. Хитрая жопа, но удобно.
Смотри, как это выглядит на практике:
import asyncio
async def fetch_data(source: str, delay: int) -> str:
print(f"Начинаю загрузку из {source}...")
await asyncio.sleep(delay)
result = f"Данные из {source}"
print(f"Загрузка из {source} завершена.")
return result
async def main():
# Запускаем обе корутины конкурентно
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("DB", 1)
)
print(f"nВсе результаты: {results}")
# Вывод покажет, что DB завершит работу раньше,
# но gather дождется завершения API.
# >> Начинаю загрузку из API...
# >> Начинаю загрузку из DB...
# >> Загрузка из DB завершена.
# >> Загрузка из API завершена.
# >>
# >> Все результаты: ['Данные из API', 'Данные из DB']
asyncio.run(main())
И главное, запомни разницу, а то все путаются. Есть ещё asyncio.create_task() — она просто швыряет корутину в цикл событий и говорит «делай что хочешь», возвращая тебе объект Task. А gather() — это уже высший пилотаж, когда тебе надо не просто запустить, а ещё и дождаться всех этих пидарасов и собрать с них урожай. Удобно, ёпта.