1async def fetch(name, secs):2 print("start", name)3 await asyncio.sleep(secs)4 print("done", name)5async def main():6 await asyncio.gather(7 fetch("A", 1),8 fetch("B", 2),9 fetch("C", 3))0 s1 s2 s3 s4 s5 s6 sone by oneABC6 sasyncABC3 sPython asyncioNetworkABCCPU
Code card: the asyncio program; highlighted lines are the ones explainedEvent loop: runs one task at a time on one threadReady queue and network bays: where tasks wait to run, and wait for dataTimeline: one by one (the sum) against overlapping (the slowest)Task tokens hop along each step's arrow: from the gather() line into the ready queue, onto the event loop to run, out to a parking bay at the network when they reach await, and back through the queue when their wait is over. The panel at the back compares the same three downloads done one by one (1 + 2 + 3 = 6 s) with the overlapping async run (3 s, the slowest). While every task is parked, the loop asks the operating system which sockets are ready (select, epoll or kqueue, through Python's selectors module) and sleeps until one is, or until the next timer is due.

Why waiting hurts

  1. A download spends most of its time waiting for the network to answer. Three downloads of 1, 2 and 3 seconds, done one after another, take 6 seconds in total, and the program sits idle nearly all that time.

Tasks and the loop

Waiting together

Beyond Python

Code and output

;; step 1/10 · Waiting wastes time
import time

def fetch(name, secs):
    print("start", name)
    time.sleep(secs)     # blocks: nothing else can run
    print("done", name)

fetch("A", 1); fetch("B", 2); fetch("C", 3)
# about 1 + 2 + 3 = 6 seconds

The full program also needs import asyncio at the top and asyncio.run(main()) at the bottom; asyncio.run() starts the event loop and runs main() on it. Strictly, the ready queue holds callbacks, not tasks. A parked task is resumed by a callback that runs its next step, which the loop schedules with call_soon() once the thing it awaits is done. asyncio.sleep() stands in for a network wait. A real download would use asyncio streams or a library such as aiohttp, and the loop treats it the same way: the task parks until its data arrives.