C.W.K.
Stream
Lesson 01 of 10 · published

Why Async? The Event Loop Problem

~12 min · async, event-loop, asyncio

Level 0Scout
0 XP0/80 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The blocking call kills cooperative scheduling

Python's asyncio runs everything on a single thread by default. When one task is waiting on I/O (a database read, an HTTP request), the event loop can run other tasks. That's the entire premise.

The catch: asyncio only sees waits when you await. A synchronous call that blocks for 200ms on disk freezes the entire event loop for that 200ms — every other request, every other task, every websocket frame.

Plain sqlite3.connect(...) + conn.execute(...) are synchronous C calls. Inside an async web app, every database query stalls the event loop. With one user it doesn't matter. With 100 concurrent users it falls over.

Two solutions:

  • aiosqlite — runs SQLite in a background thread and exposes async execute/fetchone/etc. Awaiting them yields to the event loop while the DB call runs on the thread.
  • asyncio.to_thread — manually offload sync calls. Works but you'll reinvent aiosqlite's connection-management story.
Self-reference: Pippa's backend (backend/store/conversations.py) uses aiosqlite for exactly this reason. SSE streaming from Claude must keep yielding to the event loop while a conversation is being persisted; a sync write would visibly stutter the UI.

Code

What blocking does to an async loop·python
import asyncio, sqlite3, time

async def heartbeat():
    while True:
        print('tick', time.strftime('%H:%M:%S'))
        await asyncio.sleep(0.1)

async def slow_sync_query():
    conn = sqlite3.connect('big.db')
    conn.execute('SELECT count(*) FROM huge_table').fetchone()  # blocks event loop

async def main():
    heart = asyncio.create_task(heartbeat())
    await slow_sync_query()    # heartbeat freezes for the whole call
    heart.cancel()

asyncio.run(main())

External links

Exercise

Reproduce the freeze: build a tiny FastAPI or aiohttp server with one endpoint that does a slow synchronous SQLite query, and another that's an instant async response. Send concurrent requests to the slow endpoint and measure how the fast one's latency degrades. Then swap the slow endpoint to use aiosqlite and re-measure.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.