patternpythonModeratepending
Python asyncio.gather vs TaskGroup for Concurrent Tasks
Viewed 0 times
asyncio.gatherTaskGroupstructured concurrencysemaphoreconcurrentExceptionGroup
Problem
Need to run multiple async operations concurrently in Python. asyncio.gather and TaskGroup both do this but have different error handling and cancellation behavior.
Solution
Comparison and usage patterns:
When to use which:
import asyncio
# === asyncio.gather (classic) ===
async def fetch_all_gather():
results = await asyncio.gather(
fetch_users(),
fetch_products(),
fetch_orders(),
return_exceptions=True # Don't fail on first error
)
# results is a list in order: [users, products, orders]
# With return_exceptions=True, failed tasks return the exception
for r in results:
if isinstance(r, Exception):
print(f'Task failed: {r}')
# === TaskGroup (Python 3.11+, recommended) ===
async def fetch_all_taskgroup():
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(fetch_users())
t2 = tg.create_task(fetch_products())
t3 = tg.create_task(fetch_orders())
# All tasks are done when we reach here
# If ANY task fails, ALL are cancelled and ExceptionGroup is raised
users = t1.result()
products = t2.result()
orders = t3.result()
# === Handling TaskGroup errors ===
async def safe_fetch():
try:
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(fetch_users())
t2 = tg.create_task(fetch_products())
except* ValueError as eg:
for exc in eg.exceptions:
print(f'ValueError: {exc}')
except* ConnectionError as eg:
for exc in eg.exceptions:
print(f'Connection failed: {exc}')
# === Bounded concurrency (semaphore) ===
async def fetch_many_urls(urls, max_concurrent=10):
sem = asyncio.Semaphore(max_concurrent)
async def bounded_fetch(url):
async with sem:
return await fetch_url(url)
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(bounded_fetch(u)) for u in urls]
return [t.result() for t in tasks]When to use which:
gather: Simple fire-and-forget, need return_exceptions, Python < 3.11TaskGroup: Structured concurrency, proper cancellation, Python 3.11+TaskGroupis generally preferred - it ensures no task is accidentally orphaned
Why
TaskGroup implements structured concurrency: all tasks are guaranteed to complete (or be cancelled) before exiting the
async with block. gather can leave orphaned tasks on exceptions.Gotchas
- TaskGroup raises ExceptionGroup, not a single exception - use except* syntax
- gather with return_exceptions=False (default) cancels remaining tasks on first failure
Context
Running concurrent async tasks in Python
Revisions (0)
No revisions yet.