Knowing the machinery explains starvation, ordering surprises and how to integrate other event sources.
An event loop is a loop over two things: a queue of ready callbacks and an I/O selector (epoll, kqueue or IOCP). Each iteration it computes a timeout from the nearest scheduled timer, polls the selector, turns ready I/O events and due timers into callbacks, then runs every ready callback once.
Everything is callbacks. loop.call_soon(cb) queues one; call_later / call_at schedule timers.
A Future is a placeholder for a result with a list of done-callbacks. A Task drives a coroutine: its __step method calls coro.send(None), which runs the coroutine until it hits await on something not ready. A not-ready Future yields itself up the await chain to the Task. The Task registers a done-callback on that Future and returns to the loop. When the Future gets a result (for example because the selector reported the socket readable), the callback schedules __step again, and the coroutine resumes.
Consequences:
await coro()on an already-ready path never yields: no other task runs.- A long synchronous stretch delays every callback: starvation.
asyncio.sleep(0)yields exactly once, letting queued callbacks run.
loop.create_future() plus set_result lets you bridge callback APIs into awaitables.
Example
import asyncio
async def main():
loop = asyncio.get_running_loop()
order = []
loop.call_soon(order.append, "call_soon")
loop.call_later(0.01, order.append, "call_later")
fut = loop.create_future() # bridge a callback into await
loop.call_later(0.02, fut.set_result, "future value")
async def child():
order.append("child task")
asyncio.create_task(child())
order.append("main before await")
print(await fut) # future value
print(order)
asyncio.run(main())
# ['main before await', 'call_soon', 'child task', 'call_later']Remember
- The loop runs ready callbacks, then polls I/O with a timeout from the next timer
- Tasks step coroutines with send(); awaiting a pending Future suspends the task
- Code between awaits runs without interruption
Watch out for
- Assuming every await yields to other tasks; awaiting a coroutine that finishes immediately never switches.