Blocking calls in async def
The gotcha
Putting a synchronous blocking call (time.sleep, requests.get, open().read() on a slow disk) inside an async def function blocks the entire event loop. No other coroutine runs until it returns. The function looks async; it isn’t.
Minimal repro
import asyncio
import time
async def slow_task(name):
print(f"{name} start")
time.sleep(2) # blocks the loop
print(f"{name} done")
async def main():
await asyncio.gather(
slow_task("A"),
slow_task("B"),
slow_task("C"),
)
asyncio.run(main())
You’d expect ~2 seconds total (three tasks running concurrently). You get ~6 seconds — they execute serially because time.sleep doesn’t yield to the loop.
Why it happens
asyncio is single-threaded cooperative concurrency. Each coroutine runs until it hits an await, at which point it yields control to the loop. time.sleep is a C function that suspends the OS thread — there’s no yield point for the loop to schedule something else.
The same applies to:
requests.get(...)(usehttpx.AsyncClientoraiohttp)open(...).read()on slow filesystems (useaiofiles)psycopg2.execute(...)(useasyncpgor async SQLAlchemy)redis.Redis().get(...)(useredis.asyncio)- CPU-bound work (use a process pool)
How to fix
1. Use the async version of the library
async def slow_task(name):
await asyncio.sleep(2) # yields to the loop
import httpx
async with httpx.AsyncClient() as client:
r = await client.get(url) # instead of requests.get
This is always the first thing to try.
2. Run blocking code in a thread
When no async version exists (3rd-party C library, legacy code):
import asyncio
def blocking_lib_call(x):
# synchronous, can't be made async
return some_lib.process(x)
async def main():
result = await asyncio.to_thread(blocking_lib_call, 42)
asyncio.to_thread (3.9+) runs the function in a thread pool and yields the coroutine that resolves to its return value. The event loop keeps running other tasks while the thread blocks.
Older Python:
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, blocking_lib_call, 42)
3. CPU-bound work — use a process pool
Threads don’t help for CPU-bound work due to the GIL. Use processes:
from concurrent.futures import ProcessPoolExecutor
async def main():
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(pool, cpu_heavy_fn, data)
How to detect blocking calls
asyncio.run(main(), debug=True)
Or set the env var:
PYTHONASYNCIODEBUG=1 python app.py
Debug mode logs warnings when callbacks take longer than slow_callback_duration (default 0.1s), and when coroutines are never awaited. In production, aiomonitor or APM tools (Datadog, Sentry) flag long-blocking event-loop callbacks.
Common variants
time.sleep masquerading as asyncio.sleep:
import time as asyncio # someone's terrible alias
await asyncio.sleep(1) # this is time.sleep — TypeError actually, but if it weren't...
Mixing sync ORM with async framework:
@app.get("/users")
async def get_users():
return User.query.all() # SQLAlchemy sync → blocks!
This is the #1 FastAPI / async-Django performance bug. Either go fully async (async with AsyncSession() as s: await s.execute(...)) or use a sync route handler — but don’t mix.
Logging file handlers:
logging.FileHandler writes synchronously. Under high throughput on slow disks, this blocks the loop. Use QueueHandler + QueueListener to offload, or aiologger.
Interview angle
- Q: “What’s wrong with
time.sleep(1)inside anasync def?” — blocks the entire event loop; other coroutines starve. - Q: “How do you call a synchronous library from async code?” —
asyncio.to_thread(orloop.run_in_executor). - Follow-up: “When would you use a thread pool vs a process pool?” — threads for I/O-blocking sync libs; processes for CPU-bound work (GIL bypass).
- Follow-up: “How do you detect blocking calls in production?” —
asynciodebug mode, APM with event-loop monitoring, profile withaiomonitor.
See 09_async_def_returns_coroutine.md, 04_async_concurrency/08_cpu_io_tasks.md, 04_async_concurrency/12_taskgroup_structured_concurrency.md, 02_python_core/performance/05_async_optimization.md.