stackademic

The leading education platform for anyone with an interest in software development.

Async/Await

Write concurrent I/O-bound code with coroutines, the event loop, and asyncio

Overview

async/await lets you write concurrent code that cooperatively yields control while waiting on I/O, all on a single thread. An async def function is a coroutine; calling it returns a coroutine object that only runs when driven by an event loop. This model shines for high-latency I/O (network, disk, databases) where threads would mostly sit idle.

Syntax / Usage

You define coroutines with async def, pause them with await, and run them with asyncio.run. await can only appear inside a coroutine.

import asyncio

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)  # non-blocking pause
    return f"{name} done"

async def main() -> None:
    # Run coroutines concurrently and collect results in order
    results = await asyncio.gather(
        fetch("a", 1.0),
        fetch("b", 0.5),
    )
    print(results)

asyncio.run(main())

await suspends the current coroutine until the awaited awaitable completes, freeing the loop to run others.

Examples

Bound concurrency with a semaphore so you never exceed N in-flight tasks:

import asyncio

async def worker(sem: asyncio.Semaphore, item: int) -> int:
    async with sem:
        await asyncio.sleep(0.1)
        return item * item

async def main() -> None:
    sem = asyncio.Semaphore(3)
    tasks = [worker(sem, i) for i in range(10)]
    print(await asyncio.gather(*tasks))

asyncio.run(main())

Enforce a timeout and handle cancellation cleanly:

import asyncio

async def slow() -> str:
    await asyncio.sleep(5)
    return "finished"

async def main() -> None:
    try:
        result = await asyncio.wait_for(slow(), timeout=1.0)
    except asyncio.TimeoutError:
        result = "timed out"
    print(result)

asyncio.run(main())

Common Mistakes

  • Calling a coroutine without await, which creates but never runs it (and warns "coroutine was never awaited")
  • Using blocking calls like time.sleep or synchronous requests inside a coroutine, which stalls the whole loop
  • Calling asyncio.run more than once or from inside a running loop
  • Awaiting tasks sequentially in a loop when asyncio.gather would run them concurrently
  • Forgetting to await or cancel background tasks, leading to "Task was destroyed but it is pending" warnings

See Also

python-concurrency python-iterators-protocol python-generators