Codectionary / Developer documentation / Python

async and await

Define coroutines and cooperate with an event loop while awaiting operations that may suspend.

Understand the contract

async def defines a coroutine function. Calling it creates a coroutine object; it does not immediately run the body to completion. Awaiting it or scheduling it with an event loop is what drives execution. await obtains an awaitable's result and allows suspension while that operation is incomplete.

Async code supports cooperative concurrency, especially for waiting on I/O. It is not automatic CPU parallelism. A long calculation or blocking time.sleep call inside a coroutine can prevent other tasks on that loop from progressing.

Follow the runtime behavior

asyncio.run starts and manages a loop for a top-level script, executes the supplied coroutine, and closes its loop afterward. Each example is a complete Python 3.11+ script and uses sleep(0) only as a deterministic opportunity to yield control.

asyncio.run cannot be nested in an already running event loop, including many notebook or browser environments. There, use the host's supported top-level await main() instead of the final asyncio.run(main()) line. Do not nest another loop runner to work around the restriction.

Apply it and practise

Use await for operations with asynchronous APIs. Ordinary files and CPU work do not become nonblocking just because their surrounding function is async. Keep a single clear top-level runner in a normal script.

Exercise: define async double(value), await it for 3 and 5 inside main, and print the results in order. Explain why those two direct awaits are sequential, then compare with task scheduling in the related topic.

Syntax

async def main():
    await asyncio.sleep(0)

asyncio.run(main())

Examples

Await a coroutine result

Use this structure when a higher-level coroutine needs a result before continuing.

import asyncio

async def answer():
    await asyncio.sleep(0)
    return 42

async def main():
    value = await answer()
    print(value)

asyncio.run(main())
  1. main awaits answer and receives its return value. sleep(0) cooperatively yields; no real elapsed-time measurement or external service is required.
  2. Use this structure when a higher-level coroutine needs a result before continuing.
  3. Run as a script. In a running notebook or browser loop, replace asyncio.run(main()) with supported top-level await main().
  4. Expected output: 42

Trace sequential awaits

Use sequential awaits when later operations depend on earlier completion.

import asyncio

async def step(name):
    print(f"start {name}")
    await asyncio.sleep(0)
    print(f"end {name}")

async def main():
    await step("A")
    await step("B")

asyncio.run(main())
  1. The first await must finish before main reaches the second statement. Yielding within A does not schedule B, because B has not yet been called.
  2. Use sequential awaits when later operations depend on earlier completion.
  3. Async syntax alone does not make these steps concurrent. asyncio.run also cannot be nested inside an already running host loop.
  4. Expected output: start A end A start B end B

Catch an asynchronous failure

Handle expected I/O or decoding failures at the coroutine layer that can recover.

import asyncio

async def load():
    await asyncio.sleep(0)
    raise ValueError("invalid record")

async def main():
    try:
        await load()
    except ValueError as error:
        print(error)
    print("continued")

asyncio.run(main())
  1. The exception raised inside load propagates through await to main's ordinary try/except. Handling it permits the next print to execute.
  2. Handle expected I/O or decoding failures at the coroutine layer that can recover.
  3. This is a standalone script; a running notebook/browser loop requires the host's await entry point rather than nested asyncio.run.
  4. Expected output: invalid record continued

Best practices

  • asyncio.run cannot be nested in an already running event loop, including many notebook or browser environments. There, use the host's supported top-level await main() instead of the final asyncio.run(main()) line. Do not nest another loop runner to work around the restriction.
  • Use await for operations with asynchronous APIs. Ordinary files and CPU work do not become nonblocking just because their surrounding function is async. Keep a single clear top-level runner in a normal script.

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

Specifications & further reading