Python · 10/10 6 min
asyncio essentials
Coroutines that never run, gather vs sequential awaits, blocking calls and locks.
Calling a coroutine doesn't run it
async def fetch(id): ...
fetch(1) # ❌ creates a coroutine object; nothing happens
# RuntimeWarning: coroutine 'fetch' was never awaited
await fetch(1) # ✅
Same shape as a missing await in JavaScript: you get an object, not a result.
Concurrency with gather
results = [await fetch(i) for i in ids] # sequential
results = await asyncio.gather(*(fetch(i) for i in ids)) # concurrent, results in input order
results = await asyncio.gather(*tasks, return_exceptions=True) # don't fail fast
Limit fan-out with a semaphore:
sem = asyncio.Semaphore(10)
async def limited(i):
async with sem:
return await fetch(i)
Never block the loop
time.sleep(1) # ❌ freezes every task
await asyncio.sleep(1) # ✅
requests.get(url) # ❌ blocking I/O inside async code
await asyncio.to_thread(requests.get, url) # run blocking work in a thread
Tasks
task = asyncio.create_task(worker()) # starts now, runs alongside
...
await task # get the result / exception
Keep a reference to tasks you create: an unreferenced task can be garbage-collected before it finishes, and its exception is only logged.
Races still exist
Only one coroutine runs at a time, but any await is a switch point:
if self.balance >= amount: # check
await self.audit() # ← another task can run here
self.balance -= amount # act: may overdraw
Hold an asyncio.Lock around check-then-act, or don't await in between.
Entry point
asyncio.run(main()) once, at the top. Calling it inside an already-running loop raises RuntimeError.
Practise it now
Next in Python
Python gotchas