Ответ
Обе конструкции, asyncio.TaskGroup
и asyncio.gather
, предназначены для одновременного выполнения нескольких асинхронных задач, но они предлагают разный подход к управлению задачами и обработке ошибок.
TaskGroup
(появился в Python 3.11) реализует концепцию структурного параллелизма.
Ключевые различия:
1. Обработка ошибок
TaskGroup
: При возникновении исключения в одной из задач,TaskGroup
немедленно отменяет все остальные задачи в группе и выходит из контекста, пробрасывая исключение. Это гарантирует, что группа завершается целиком — либо все успешно, либо с ошибкой.asyncio.gather
: По умолчанию, если одна задача падает с ошибкой,gather
продолжает выполнять остальные. Ошибка будет выброшена только после завершения всех задач. Поведение можно изменить с помощью флагаreturn_exceptions=True
, тогдаgather
вернет результаты и исключения вместо того, чтобы их пробрасывать.
2. Синтаксис и жизненный цикл
TaskGroup
: Используется как асинхронный контекстный менеджер (async with
). Это четко определяет границы жизни группы задач. Задачи не могут "утечь" за пределы блокаasync with
.asyncio.gather
: Это обычная функция, которая принимает корутины илиFuture
и возвращает результат, когда все они завершены.
3. Добавление задач
TaskGroup
: Задачи добавляются динамически внутри блока с помощью методаcreate_task()
.asyncio.gather
: Принимает заранее известный список корутин в качестве аргументов.
Примеры кода
asyncio.TaskGroup
— надежная обработка ошибок
import asyncio
async def task_that_fails():
await asyncio.sleep(1)
raise ValueError("Something went wrong")
async def long_running_task():
print("Long task started")
await asyncio.sleep(5)
print("Long task finished") # Этот код не выполнится
async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(task_that_fails())
tg.create_task(long_running_task())
except* ValueError as e:
print(f"Caught exception: {e.exceptions[0]}")
# Вывод: Long task started -> Caught exception: ValueError('Something went wrong')
# Вторая задача была отменена.
asyncio.run(main())
asyncio.gather
— выполнение до конца
async def main_gather():
try:
# gather дождется выполнения long_running_task
await asyncio.gather(
task_that_fails(),
long_running_task()
)
except ValueError as e:
print(f"Caught exception: {e}")
# Вывод: Long task started -> Long task finished -> Caught exception: Something went wrong
asyncio.run(main_gather())
Вывод:
- Используйте
TaskGroup
для связанных задач, где сбой одной должен приводить к отмене всей операции. Это более современный и безопасный подход. - Используйте
asyncio.gather
для простого параллельного запуска независимых задач, когда вам нужно дождаться их всех, даже если некоторые завершатся с ошибкой.