Never block the event loop. Run blocking/CPU code in thread or process pools.
async def handler():
time.sleep(1) # โ FREEZES the event loop
result = requests.get(url) # โ also blocks the loop
heavy_json = json.loads(big_data) # โ CPU-heavy
return result
# While one request sleeps for 1s, ALL OTHER REQUESTS stall
# One user's slow request = entire server frozen
import asyncio
def blocking_io():
# Simulate legacy sync code: file read, DB query, etc.
time.sleep(1)
return "done"
async def handler():
# asyncio.to_thread runs blocking_io in a thread pool
# Loop is free to run other coroutines
result = await asyncio.to_thread(blocking_io)
return result
async def main():
# All 3 handlers run concurrently (requests don't block each other)
results = await asyncio.gather(
handler(), handler(), handler()
)
from concurrent.futures import ProcessPoolExecutor
import asyncio
def cpu_heavy(n):
# Heavy computation
return sum(i * i for i in range(n))
async def handler():
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
# Offload to separate process
result = await loop.run_in_executor(pool, cpu_heavy, 5_000_000)
return result
async def main():
results = await asyncio.gather(
handler(), handler(), handler()
)
| Type of Work | Tool | Why |
|---|---|---|
| Sync I/O (file, DB, legacy lib) | asyncio.to_thread() |
Threads can block safely; same process |
| Heavy CPU, math, encoding | ProcessPoolExecutor |
Multiple processes bypass GIL; real parallelism |
| Network I/O (API calls) | async-compatible lib (httpx, aiohttp) |
Don't block the loop; designed for async |
Never call a blocking function directly in async code. If you must, offload it.
await asyncio.to_thread(fn) or loop.run_in_executor(pool, fn)