Ответ
Обе конструкции, 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для простого параллельного запуска независимых задач, когда вам нужно дождаться их всех, даже если некоторые завершатся с ошибкой.
Ответ 18+ 🔞
Давай разберемся, что тут у нас за два зверя, а то глаза разбегаются. asyncio.TaskGroup и asyncio.gather — оба, вроде как, делают одно: запускают кучу асинхронных дел одновременно. Но подход у них, блядь, как небо и земля, и если выбрать не тот — потом будешь локти кусать.
Представь, что TaskGroup — это новый, строгий прораб на стройке (появился в Python 3.11). А gather — это старый, пофигистский бригадир. Сейчас поймёшь, в чём разница.
Главные отличия, или где собака зарыта
1. Как они относятся к косякам (обработка ошибок)
TaskGroup: Этот, сука, максималист. Один работяга на стройке уронил кирпич на ногу и заорал — прораб сразу орет "ВСЁ, СТОП, РАЗБЕГАЙСЯ!" и отменяет все остальные задачи. Группа либо вся работает, либо вся летит в тартарары. Чётко, структурно, без сюрпризов.asyncio.gather: А этот старый пердун так не умеет. Один упал с лесов — бригадир продолжает свистеть и остальные работают. Ошибку он тебе покажет только когда ВСЕ достроят, хоть и криво. Можно, конечно, сказать емуreturn_exceptions=True, тогда он просто принесёт тебе пачку результатов, а в ней — один труп с исключением. Но это уже твои проблемы.
2. Как с ними работать (синтаксис и жизнь)
TaskGroup: Работает черезasync with. Это как зайти на охраняемую территорию — задачи создаются прямо внутри блока методомcreate_task(). Вышел из блока — всё, задач нет, они не могут сбежать и висеть в памяти как проклятые.asyncio.gather: Просто функция, ей скармливают список корутин заранее и говорят: "Жди всех". Динамически ничего не добавишь.
3. Когда что добавлять
TaskGroup: Задачи плодятся прямо по ходу пьесы, динамически. Увидел работу — кинул в группу.asyncio.gather: Нужно заранее знать, кого и сколько ты собрался гонять.
Смотри, как это выглядит в коде
asyncio.TaskGroup — дисциплина и порядок
import asyncio
async def task_that_fails():
await asyncio.sleep(1)
raise ValueError("Чё-то пошло не так, ёпта!")
async def long_running_task():
print("Долгая задача началась")
await asyncio.sleep(5)
print("Долгая задача завершилась") # Этой строчке не бывать, увы!
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"Поймал исключение: {e.exceptions[0]}")
# Вывод: "Долгая задача началась" -> "Поймал исключение: ValueError('Чё-то пошло не так, ёпта!')"
# Вторая задача была пристрелена на месте. Жестоко, но справедливо.
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"Поймал исключение: {e}")
# Вывод: "Долгая задача началась" -> "Долгая задача завершилась" -> "Поймал исключение: Чё-то пошло не так, ёпта!"
# Всё выполнилось, но исключение прилетело в конце. Как после вечеринки — весело было, а утром похмелье.
asyncio.run(main_gather())
Итог, или что куда пихать
Короче, выбор простой, как три копейки:
- Берёшь
TaskGroup, когда задачи — как команда: один споткнулся, падают все. Нужна чистая отмена и порядок. Современно, модно, молодёжно. - Тащишь
asyncio.gather, когда задачи друг про друга не знают и им похуй на соседа. Запустил и забыл, результат соберёшь потом, даже если там одни трупы.
Вот и вся магия, ебать. Выбирай с умом, а то потом дебажить эти гонки и утечки — волосы на жопе поседеют.