🚀AdvancedConcurrency
Asyncio Async/Await Deep Dive
Event loops, coroutines, Tasks, gather, timeouts, and building real async pipelines.
Overview
`asyncio` is Python's built-in framework for writing single-threaded concurrent code using coroutines. Instead of blocking when waiting on I/O, a coroutine suspends itself (`await`) and yields control back to the event loop, which runs another coroutine. The result: thousands of concurrent I/O operations in one thread, with no GIL issues and less overhead than threads.
**Coroutine**: an `async def` function. Calling it returns a coroutine object it doesn't run until awaited.
**Task**: a coroutine wrapped by `asyncio.create_task()` that is scheduled to run concurrently.
**Event Loop**: the scheduler that drives all coroutines. `asyncio.run(main())` is the standard entry point.
Key patterns: `await asyncio.gather(*coros)` to run multiple coroutines concurrently and collect results; `asyncio.wait_for(coro, timeout)` for deadlines; `asyncio.Queue` for async producer-consumer; `async for` / `async with` for async iteration and context managers.
Key Points
- •`async def` defines a coroutine; `await expr` suspends until expr resolves
- •`asyncio.run(main())` the standard way to start an async program (Python 3.7+)
- •`asyncio.create_task(coro)` schedules a coroutine as a Task to run concurrently
- •`await asyncio.gather(t1, t2, t3)` run multiple awaitables concurrently; returns list of results
- •`await asyncio.sleep(n)` async sleep that yields control (unlike time.sleep which blocks)
- •`asyncio.wait_for(coro, timeout=5)` cancel the coroutine if it takes too long
- •`asyncio.Queue` async-safe queue; use `await q.put()` and `await q.get()`
- •`async with` and `async for` work with classes that implement `__aenter__/`__aexit__` or `__aiter__/`__anext__`
Examples
Coroutines running concurrently with gather
import asyncio
async def fetch(name, delay):
print(f"start {name}")
await asyncio.sleep(delay) # non-blocking wait
print(f"done {name}")
return f"result:{name}"
async def main():
results = await asyncio.gather(
fetch("A", 1),
fetch("B", 2),
fetch("C", 0.5),
)
print(results)
asyncio.run(main())
# Completes in ~2 s (longest task), not 3.5 s (sum)Tasks for fire-and-forget with cancellation
import asyncio
async def worker(n):
await asyncio.sleep(n)
return n * n
async def main():
t1 = asyncio.create_task(worker(1))
t2 = asyncio.create_task(worker(2))
t3 = asyncio.create_task(worker(3))
# cancel t3 before it finishes
t3.cancel()
results = await asyncio.gather(t1, t2, return_exceptions=True)
print(results) # [1, 4]
asyncio.run(main())Async producer-consumer with asyncio.Queue
import asyncio
async def producer(q):
for i in range(6):
await q.put(i)
await asyncio.sleep(0.1)
await q.put(None)
async def consumer(q):
while True:
item = await q.get()
if item is None:
break
print(f"consumed {item}")
q.task_done()
async def main():
q = asyncio.Queue(maxsize=3)
await asyncio.gather(producer(q), consumer(q))
asyncio.run(main())Common Mistakes
- Calling `time.sleep()` inside a coroutine blocks the entire event loop; always use `await asyncio.sleep()`
- Not awaiting a coroutine `fetch("A")` creates a coroutine object but never runs it; you must `await` it
- Using `asyncio.run()` inside an already-running event loop (e.g. Jupyter) use `await` directly or `asyncio.get_event_loop().run_until_complete()` instead
Note
asyncio shines for network I/O (HTTP clients with aiohttp, databases with asyncpg, etc.). For CPU work use `loop.run_in_executor(None, sync_fn)` to run synchronous code in a thread pool without blocking the event loop.
Exercises
Write Python code to solve each exercise. Run it in your browser and check your output.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4