Ответ
Обе конструкции, 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для простого параллельного запуска независимых задач, когда вам нужно дождаться их всех, даже если некоторые завершатся с ошибкой.