Python asyncio event loop
asyncio is a single-threaded cooperative scheduler. async functions return coroutines; await yields control to the loop; the loop drives I/O via OS selectors.
asyncio is Python's built-in event loop, modeled on JavaScript's. One thread, cooperative scheduling, async functions that yield with await. Same shape as Node.js, different syntax.
The core pieces
- Coroutine.
async def foo(): ...produces a coroutine when called. It is not yet running. You schedule it withawait,asyncio.create_task, orasyncio.run. - Event loop. The scheduler. Holds a queue of ready tasks and a selector watching file descriptors. Runs one task until it yields, then picks the next.
- Task. A wrapper around a coroutine that the loop can run.
asyncio.create_task(coro)schedules it. - Future. A placeholder for a value that will be available later. Coroutines
awaitfutures.
How a tick works
- Loop checks for ready callbacks (timer expired, I/O ready) and runs them.
- Each callback may resume a task. A task runs synchronously until it hits an
awaiton something not yet ready, then yields. - Loop polls the OS selector (
epoll/kqueue/select) for new I/O events. - Repeat.
import asyncio
async def fetch(url, n):
print(f"start {n}")
await asyncio.sleep(1)
print(f"done {n}")
async def main():
await asyncio.gather(fetch("a", 1), fetch("b", 2), fetch("c", 3))
asyncio.run(main())
# start 1, start 2, start 3 (concurrent)
# done 1, done 2, done 3 (after 1 second total, not 3)What await does
await x says: "suspend this coroutine until x resolves, and let the loop do other things." x must be awaitable: another coroutine, a Task, or a Future. The coroutine's stack frame is saved; when x resolves, the loop resumes the coroutine.
Behind the scenes, await translates to yield from semantics. The whole async/await syntax is sugar over generator-based coroutines (which existed in Python before async def).
Where it talks to the OS
The event loop wraps a selector (Python's selectors module): EpollSelector on Linux, KqueueSelector on BSD/macOS, SelectSelector as fallback. It registers file descriptors for read/write events and calls select() (or equivalent) to block until something is ready.
Sockets, pipes, and subprocesses are exposed via async-aware wrappers. File system I/O is not natively async on most OSes, so asyncio uses a thread pool (via loop.run_in_executor) for blocking calls.
The model: one thread, one loop, many cooperating coroutines. Yields at every await. Never blocks for sync calls. Same scaling model as Node.
Learn more
- DocsPython docs: asynciopython.org
- Docs
- Talk