Skip to main content

09.3 - Asynchronous Programming with asyncio

Theory 30 min Advanced

What is Async Programming?

Async I/O runs multiple tasks concurrently in a single thread by voluntarily yielding control while waiting for I/O:

Thread 1 (sync):    ─── Wait ─── ─── Wait ─── ─── Wait ───
task1 task2 task3

Event loop (async): ─work─ yield ─work─ yield ─work─ yield
task1 →wait task2 →wait task3 →wait
←done ←done ←done

Core Concepts

import asyncio

# A coroutine function (does not run when called)
async def greet(name, delay):
await asyncio.sleep(delay) # non-blocking wait
print(f"Hello, {name}!")

# Run a coroutine
asyncio.run(greet("Alice", 1))

# Run multiple concurrently with gather
async def main():
# All 3 run concurrently — total ~2s, not 6s!
await asyncio.gather(
greet("Alice", 2),
greet("Bob", 1),
greet("Charlie", 1.5),
)

asyncio.run(main())

async for and async with

import asyncio
import aiohttp # pip install aiohttp

# Async context manager
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()

# Async generator
async def paginate(url, pages=3):
async with aiohttp.ClientSession() as session:
for page in range(1, pages + 1):
async with session.get(f"{url}?page={page}") as r:
yield await r.json()

async def main():
async for data in paginate("https://api.example.com/items"):
print(data)

asyncio.gather vs asyncio.create_task

async def main():
# gather — concurrent, waits for all
results = await asyncio.gather(
fetch("https://api1.com"),
fetch("https://api2.com"),
return_exceptions=True # don't fail all if one fails
)

# create_task — schedule independently
task1 = asyncio.create_task(fetch("api1.com"))
task2 = asyncio.create_task(fetch("api2.com"))
# ... do other things ...
result1 = await task1
result2 = await task2

# asyncio.as_completed — process results as they arrive
for coro in asyncio.as_completed([fetch(url) for url in urls]):
result = await coro
process(result)

When to Use asyncio vs threading vs multiprocessing

WorkloadBest tool
Many I/O operations (APIs, DB)asyncio
Mixed I/O with blocking librariesthreading
CPU-intensive computationmultiprocessing
Simple scriptsthreading (simpler)

Key Vocabulary

TermDefinition
CoroutineFunction defined with async def — doesn't run until awaited
awaitSuspend current coroutine until the awaitable completes
Event loopCentral dispatcher that runs and schedules coroutines
asyncio.run()Entry point — creates event loop and runs a coroutine
asyncio.gather()Run multiple coroutines concurrently and collect results
asyncio.create_task()Schedule a coroutine as an independent task
aiohttpAsync HTTP client/server library

Summary

  • async def defines a coroutine; await pauses it until an async operation completes
  • asyncio.run(coro) is the entry point for async programs
  • asyncio.gather(*coros) runs multiple coroutines concurrently in a single thread
  • Use asyncio for I/O-bound work with many concurrent operations
  • aiohttp provides async HTTP — ideal for calling multiple APIs in parallel