async / await
Calling an async def function does not run it — it creates a coroutine. await runs a coroutine to completion, letting the event loop run other tasks whenever it has to wait.
async def main(): result = await coro()
asyncio.run(main())
async for item in aiter_obj: ...
async with lock: ...
Demo
import asyncio xs = [1, 2, 3] ys = [4, 5] log = [] async def worker(name, items): for x in items: log.append(f'{name}{x}') await asyncio.sleep(0) async def main(): await worker('a', xs) await worker('b', ys) asyncio.run(main()) log
Compare the first two tabs with the same inputs. Plain sequential awaits give all of a, then all of b. With gather, both workers are tasks on the event loop and each await asyncio.sleep(0) lets the other one take a step, so the log alternates a, b, a, b until the shorter one runs out. That order is deterministic: the loop runs ready tasks first in, first out.
Syntax slots
| Name | Type | Required | Description |
|---|---|---|---|
| async def | definition | yes | Defines a coroutine function. await, async for and async with are only allowed inside one. |
| await expr | expression | no | expr must be awaitable (coroutine, Task, Future). Suspends this coroutine until the result is ready. |
| async for | statement | no | Loops over an asynchronous iterable (__aiter__ / __anext__), e.g. an async generator. |
| async with | statement | no | Uses an asynchronous context manager (__aenter__ / __aexit__), e.g. asyncio.Lock or an HTTP session. |
Common patterns
import asyncio async def main(): ... if __name__ == "__main__": asyncio.run(main())
async def main(): results = await asyncio.gather(fetch(a), fetch(b), fetch(c))
async def main(): async with asyncio.TaskGroup() as tg: t1 = tg.create_task(fetch(a)) t2 = tg.create_task(fetch(b)) print(t1.result(), t2.result())
async def load(url): async with asyncio.timeout(5): return await fetch(url)
Examples
Pitfalls
import asyncio async def get(): return 41 async def main(): c = get() try: return c + 1 finally: c.close() asyncio.run(main())
import asyncio async def get(): return 41 async def main(): return await get() + 1 asyncio.run(main())
import asyncio async def inner(): return 1 async def main(): c = inner() try: return asyncio.run(c) finally: c.close() asyncio.run(main())
import asyncio async def inner(): return 1 async def main(): return await inner() asyncio.run(main())
import asyncio log = [] async def job(name): log.append(name + ' start') await asyncio.sleep(0) log.append(name + ' end') async def main(): await job('a') await job('b') asyncio.run(main()) log
import asyncio log = [] async def job(name): log.append(name + ' start') await asyncio.sleep(0) log.append(name + ' end') async def main(): await asyncio.gather(job('a'), job('b')) asyncio.run(main()) log
When to use
- Waiting on many network or I/O operations at once (HTTP clients, websockets, database drivers with async APIs)
- Servers handling many connections in one thread
- Timeouts and cancellation of waits
- CPU-heavy work → multiprocessing or concurrent.futures (async does not add CPU parallelism)
- Libraries with only blocking APIs → threads, or asyncio.to_thread()
- A short script that does one thing at a time → plain synchronous code
Notes
FAQ
You called an async def function but never awaited the coroutine it returned, so its body never ran. Python emits RuntimeWarning: coroutine 'name' was never awaited when the unused coroutine is garbage-collected. Add await (or pass it to asyncio.run / create_task / gather).