Skillversitas

Tasks, gather and as_completed

Calling a coroutine and awaiting it immediately still runs it "inline" - the current coroutine waits for it. A Task schedules a coroutine on the event loop so it runs independently, and the current coroutine can keep doing other work or await its completion later. Tasks plus the composition helpers gather and as_completed cover almost every fan-out pattern.

Creating Tasks

import asyncio

async def slow(n):
    await asyncio.sleep(n)
    return n

async def main():
    t1 = asyncio.create_task(slow(1))
    t2 = asyncio.create_task(slow(2))
    print("tasks started, doing other work...")
    await asyncio.sleep(0.3)
    print("t1 done?", t1.done())     # False
    print("t1 ->", await t1)         # 1
    print("t2 ->", await t2)         # 2

asyncio.run(main())

create_task schedules the coroutine and returns control at once. Awaiting the task later blocks only the awaiting coroutine, not the loop. Keep a reference to the task (a local variable is enough) or the loop may garbage-collect an in-flight task.

gather

asyncio.gather runs many coroutines or tasks concurrently and returns their results as a list, in input order:

async def main():
    results = await asyncio.gather(slow(1), slow(0.5), slow(2))
    print(results)          # [1, 0.5, 2] - order matches the inputs

    # by default one exception cancels the rest; keep the others:
    results = await asyncio.gather(bad(), good(), return_exceptions=True)
    print(results)          # [Exception(...), 42]

as_completed

When you want to process results the moment each one finishes - updating a UI, streaming a report - iterate as_completed instead:

async def main():
    tasks = [asyncio.create_task(slow(d)) for d in (2, 0.5, 1)]
    for fut in asyncio.as_completed(tasks):
        print("finished:", await fut)   # 0.5 first, then 1, then 2

TaskGroup (3.11+)

Python 3.11 added asyncio.TaskGroup, a structured-concurrency container: all tasks are awaited when the block exits, and if any task raises, the others are cancelled automatically:

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(slow(1))
        t2 = tg.create_task(slow(2))
    print(t1.result(), t2.result())

Cancellation

task.cancel() asks the task to stop; CancelledError is raised at its next await. Use try/except asyncio.CancelledError for cleanup, and re-raise it. Timeouts are built on cancellation: asyncio.wait_for(coro, timeout=5) cancels the coroutine when the deadline passes.

Summary

Schedule work with create_task, combine fan-out with gather (ordered) or as_completed (eager), prefer TaskGroup on 3.11+ for automatic cleanup, and treat wait_for as your standard timeout tool.