Bridging Sync & Async

A track of P33 · Asyncio & Coroutines.

Keep blocking work off the single-threaded loop: offload to threads or processes with to_thread/run_in_executor, and bound your queues so a fast producer cannot exhaust memory (backpressure).

Your async web service is fast until someone calls a synchronous PDF library inside a request handler. Suddenly every request hangs, not just that one — because the event loop is a single thread, and one blocking call holds it hostage. This short track is about living in the real world, where async code must talk to blocking libraries and CPU-bound work without freezing the loop.

The rule is simple and absolute: never block the loop. A slow synchronous call, time.sleep, or heavy computation inside a coroutine stalls every other coroutine until it returns — and await doesn't help if the work itself never yields. The fix is to bridge out: asyncio.to_thread(fn, ...) (over run_in_executor) runs a blocking call on a worker thread, freeing the loop; the kernel rung offloads a real blocking function and proves the calls overlap. Pick the offload with the P32 lens: threads for blocking I/O, a process pool for CPU-bound work, because the GIL still applies inside those threads.

The other half is backpressure. When a producer outruns a consumer, an unbounded queue grows until memory dies; a bounded asyncio.Queue(maxsize) makes put wait when full, so the fast side is throttled to the rate the slow side can drain. That closes the concurrency arc that began in P32: async for I/O-bound fan-out, threads and processes for the blocking and CPU-bound work you bridge out to, and bounded queues so a burst can't take the process down.

Never block the loopone call freezes all coroutinesOffload with to_threadthreads for I/O, processes for CPUBackpressurea bounded queue makes put() waitP32 → P33 arc closedthe right tool for each job
Keep the single-threaded loop responsive under real, blocking work.