Lesson 19 · 19 of 20
asyncio with async and await · Python
Asynchronous Python with asyncio: coroutines with async and await, gather to run tasks together, sleep(0) and wait_for with a timeout.

A function defined with `async def` is a coroutine: calling it does not run it, it returns an object that the asyncio event loop will run. asyncio.run(main()) creates that loop, runs main() and closes the loop when it finishes.
`await` waits for another coroutine and hands over control in the meantime. check_stock fakes a warehouse query with await asyncio.sleep(...): while Madrid is busy, the loop serves Bilbao and Sevilla. That is why the three questions go out back to back and the answers arrive fastest first: Bilbao (0.01 s), Sevilla, then Madrid. The whole thing takes as long as the slowest one, not the sum.
`asyncio.gather()` starts several coroutines together and waits for all of them. Even if they finish in a different order, it returns the results in the order they were passed, so results starts with Madrid.
await asyncio.sleep(0) waits for nothing: it just gives control back to the loop. With it, packer and printer take turns on each job. That is the asyncio model: a single thread that only switches tasks at an await, so the order is predictable.
`asyncio.wait_for(..., timeout=0.05)` sets a time limit: the one-second operation is cancelled and TimeoutError is raised, which the program catches. asyncio shines when there is a lot of waiting on the network or disk; for heavy calculations it does not make anything faster.
Code · main.py
import asyncio# Three warehouses answer a stock query; each one takes a different timeDELAY = {"Madrid": 0.03, "Bilbao": 0.01, "Sevilla": 0.02}STOCK = {"Madrid": 12, "Bilbao": 0, "Sevilla": 7}async def check_stock(city, product): print(f"asking {city} about {product}...") await asyncio.sleep(DELAY[city]) # waits without blocking the rest print(f" {city} answered") return city, STOCK[city]async def worker(name, jobs): for job in jobs: print(f"{name}: {job}") await asyncio.sleep(0) # hand control back to the loopasync def main(): # gather runs them at the same time and keeps the order of the results results = await asyncio.gather(*(check_stock(c, "chair") for c in DELAY)) print("Results:", results) # Two coroutines taking turns: each await is a switch point await asyncio.gather(worker("packer", ["box #1", "box #2"]), worker("printer", ["label #1", "label #2"])) try: # give up if it takes too long await asyncio.wait_for(asyncio.sleep(1), timeout=0.05) except TimeoutError: print("Slow supplier: timed out")asyncio.run(main())Output · python3 main.py
asking Madrid about chair...
asking Bilbao about chair...
asking Sevilla about chair...
Bilbao answered
Sevilla answered
Madrid answered
Results: [('Madrid', 12), ('Bilbao', 0), ('Sevilla', 7)]
packer: box #1
printer: label #1
packer: box #2
printer: label #2
Slow supplier: timed outKey points
async defdefines a coroutine;awaitwaits without blocking.asyncio.run()starts the event loop.gatherruns several at once and keeps the results in order.- Tasks only switch at an
await. wait_forcancels whatever goes over the time limit.
Exercise
Add a fourth warehouse, Valencia, with a 0.005 s delay and 3 units, and check where it answers and where it appears in results.