Understand the contract
A Task schedules a coroutine on the running event loop. create_task lets independent work proceed while the current coroutine awaits something else. Keep a reference to created tasks and ensure their results or failures are observed; unowned background work is difficult to shut down reliably.
gather combines awaitables and returns results in argument order, regardless of which finishes first. TaskGroup, available in Python 3.11, gives a group a structured lifetime: leaving its async with block waits for all tasks and failure handling cancels remaining siblings as appropriate.
Follow the runtime behavior
Cancellation is cooperative. cancel requests cancellation; the task receives CancelledError at an opportunity to resume, and finally blocks can perform cleanup. After requesting cancellation, await the task and normally let it propagate cancellation rather than silently swallowing it inside the worker.
These examples are scripts using asyncio.run. It cannot be nested in a running notebook or browser loop; replace that last call with the host's supported top-level await main(). CPU-bound work and blocking calls still block the event loop.
Apply it and practise
Prefer TaskGroup when several tasks belong to one operation and should finish or fail together. gather does not automatically cancel all other awaitables merely because one raises; understand its failure behavior before choosing it.
Exercise: use TaskGroup to start three coroutines that each return the square of a number. After the block exits, print results in input order. Then add a controlled failure and explain how group lifetime differs from leaving tasks unobserved.
Syntax
task = asyncio.create_task(work())
result = await taskExamples
Gather scheduled results
Use this pattern to collect independent asynchronous results while retaining a predictable association with inputs.
import asyncio
async def double(value):
await asyncio.sleep(0)
return value * 2
async def main():
first = asyncio.create_task(double(3))
second = asyncio.create_task(double(1))
results = await asyncio.gather(first, second)
print(results)
asyncio.run(main())- create_task schedules both coroutines. gather awaits them and places results in the same order as its arguments, so output does not depend on completion timing.
- Use this pattern to collect independent asynchronous results while retaining a predictable association with inputs.
- Run as a script or use host-supported await main() in a running notebook/browser loop; never nest asyncio.run there.
- Expected output: [6, 2]
Give tasks a shared lifetime
Use a group to ensure all child operations finish before the parent operation returns.
import asyncio
async def label(name):
await asyncio.sleep(0)
return name.upper()
async def main():
async with asyncio.TaskGroup() as group:
first = group.create_task(label("alpha"))
second = group.create_task(label("beta"))
print(first.result(), second.result())
asyncio.run(main())- Exiting the TaskGroup waits for both tasks. Their result methods are safe afterward because successful completion has already been established by the context manager.
- Use a group to ensure all child operations finish before the parent operation returns.
- TaskGroup requires Python 3.11+. Group failures may raise ExceptionGroup; a running host loop needs await main() instead of asyncio.run.
- Expected output: ALPHA BETA
Cancel and await cleanup
Use explicit synchronization instead of timing guesses when testing cancellation and resource cleanup.
import asyncio
async def worker(started):
try:
started.set()
await asyncio.Event().wait()
finally:
print("cleanup")
async def main():
started = asyncio.Event()
task = asyncio.create_task(worker(started))
await started.wait()
task.cancel()
try:
await task
except asyncio.CancelledError:
print("cancelled")
asyncio.run(main())- The started event proves the worker entered its try block before cancellation. The worker then waits indefinitely; cancel interrupts that await, runs finally, and propagates CancelledError to main.
- Use explicit synchronization instead of timing guesses when testing cancellation and resource cleanup.
- Cancellation is a request, not forced thread termination. This standalone runner cannot be nested in a running notebook/browser loop.
- Expected output: cleanup cancelled
Best practices
- These examples are scripts using asyncio.run. It cannot be nested in a running notebook or browser loop; replace that last call with the host's supported top-level await main(). CPU-bound work and blocking calls still block the event loop.
- Prefer TaskGroup when several tasks belong to one operation and should finish or fail together. gather does not automatically cancel all other awaitables merely because one raises; understand its failure behavior before choosing it.
Runtime & versions
Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
Python language reference