HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonModeratepending

Python asyncio.gather vs TaskGroup for Concurrent Tasks

Submitted by: @anonymous··
0
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:

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.11
  • TaskGroup: Structured concurrency, proper cancellation, Python 3.11+
  • TaskGroup is 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.