Python asyncio Advanced Patterns: Tasks, Semaphores, and Timeouts
Beyond basic async/await, asyncio has patterns for limiting concurrency, handling timeouts, and managing task lifecycles. Here are the ones I use. Once you understand the basics of async/await, the next challenge is managing concurrency. Unrestricted async can overwhelm an upstream service with thousands of simultaneous requests. Tasks can hang forever without a timeout. Background tasks can leak if they are never awaited. The asyncio module provides primitives for all of these, and using them correctly is the difference between async code that works in production and async code that causes 3 AM incidents. Creating Tasks for Concurrent Work The asyncio.create_task function schedules a coroutine to run concurrently. Unlike calling a coroutine directly (which creates but does not start it), a task starts running immediately on the event loop. You await the task to get its result. import asyncio async def fetch(url): await asyncio.sleep(1) return f'data from {url}' async def main(): task1 = asyncio.create_task(fetch('api/a')) task2 = asyncio.create_task(fetch('api/b')) result1 = await task1 result2 = await task2 print(result1, result2) asyncio.run(main()) Both fetches run concurrently, so the total time is about 1 second, not 2. The asyncio.gather function does this more concisely when you have…