Desk Clarity

Everyday document & browser help

Asyncio Gather Raises but Other Tasks Continue: Own the Remaining Tasks

By ON Lab · Sources checked November 9, 2026

Separate gather’s first propagated exception from the lifetime of sibling tasks. Keep their handles and decide whether to await completion or cancel and collect them.

Reproduce the boundary in a disposable example

One gather task fails while sibling tasks keep running or appear to stop at program exit.

import asyncio
async def fail():
    raise ValueError("sample failure")
async def finish(events):
    await asyncio.sleep(0)
    events.append("finished")
    return 7
async def main():
    events = []
    tasks = [asyncio.create_task(fail()),
             asyncio.create_task(finish(events))]
    try:
        await asyncio.gather(*tasks)
    except ValueError:
        results = await asyncio.gather(*tasks, return_exceptions=True)
    assert isinstance(results[0], ValueError)
    assert results[1] == 7 and events == ["finished"]
asyncio.run(main())
print("sibling completion collected after failure")

Read the result before changing application code

The second gather keeps the demonstration alive until both results are collected. Without that wait, returning from main can trigger runner shutdown while siblings are still pending. That can make an observer attribute cancellation to gather itself. Capture both the first error and the final task states when diagnosing real jobs.

Choose the behavior your caller needs

For independent jobs, collecting exceptions as results can be appropriate; inspect each result instead of reporting the whole batch as successful. For a group that must fail together, consider TaskGroup on Python 3.11 or later. For manually owned tasks, cancel unfinished siblings and then await them with return_exceptions=True when failure should stop the group. Cancellation cannot undo external writes already completed.

Scope and limits

This example has deterministic local work and no network or production jobs. TaskGroup exception groups and cancellation cleanup need their own application tests.

Related: compare the adjacent data or execution boundary.

The embedded example was executed in an isolated local Python process on November 9, 2026. It is a reproducible boundary check, not a test of your full application.

Sources