Tasks & Gather
Tasks wrap coroutines for concurrent scheduling on the event loop. asyncio.gather awaits multiple awaitables together - the bread-and-butter of parallel I/O in asyncio.
Search across all documentation pages
Tasks wrap coroutines for concurrent scheduling on the event loop. asyncio.gather awaits multiple awaitables together - the bread-and-butter of parallel I/O in asyncio.
import asyncio
async def fetch(n: int) -> int:
await asyncio.sleep(0.1)
return n
async def main() -> None:
tasks = [asyncio.create_task(fetch(i)) for i in range(3)]
print(await asyncio.gather(*tasks))
asyncio.run(main())When to reach for this:
return_exceptions=Truecreate_taskimport asyncio
async def download(url_id: int) -> dict:
await asyncio.sleep(0.05)
if url_id == 2:
raise ValueError("bad response")
return {"id": url_id, "bytes": 100}
async def all_or_nothing() -> None:
try:
await asyncio.gather(*(download(i) for i in range(4)))
except ValueError as exc:
print("failed", exc)
async def collect_errors() -> None:
results = await asyncio.gather(
*(download(i) for i in range(4)),
return_exceptions=True,
)
for r in results:
print(type(r).__name__, r)
asyncio.run(all_or_nothing())
asyncio.run(collect_errors())What this demonstrates:
gather fails fast on first exceptionreturn_exceptions=True yields exception objects in result listcreate_task schedules them| Coroutine | Task | |
|---|---|---|
| Scheduling | Await to run | Scheduled immediately |
| Cancellation | N/A | task.cancel() |
| Inspection | Limited | .done(), .exception() |
gather - convenient fan-in, legacy-friendlyTaskGroup (3.11+) - structured concurrency, auto-cancel siblings on failurereturn_exceptions=True or TaskGroup.to_thread.task.exception() never retrieved logs warning. Fix: callback or asyncio.create_task wrapper logging failures.
| Alternative | Use When | Don't Use When |
|---|---|---|
| TaskGroup | New code, strict parent/child | Need gather's return_exceptions ergonomics |
| asyncio.as_completed | Process results as ready | Need full list at end |
| Semaphore + gather | Cap concurrency | Tiny fan-out |
create_task for coroutines on running loop; ensure_future broader (legacy interop).
Concurrent on one thread - interleaved awaits, not OS parallel threads.
asyncio.Semaphore(n) around work inside each task.
async with asyncio.timeout(5): wrapping gather or TaskGroup.
wait returns done/pending sets; gather returns values/exceptions list.
Yes - awaitables include tasks, coroutines, futures.
create_task(coro, name="fetch-user") helps debug logs (3.8+).
Add task.add_done_callback logging task.exception().
Yes - result list order matches input awaitables order.
Works but flatten when possible; semaphore at outer level for clarity.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026