Asyncio: High-Scale Concurrency
Mastering the Event Loop. How to handle 10,000 connections with a single thread.
1. The Big Idea (ELI5)
👶 Explain Like I'm 10: The Chess Grandmaster
How can one person play 50 games of chess at once? A Threads approach would be hiring 50 players.Asyncio is like a Grandmaster:
- The Move (`await`): The Grandmaster walks to Table 1, makes a move, and walks away immediately while the opponent thinks.
- The Loop: They circle the room. If Table 2 is ready, they move. If Table 3 is still thinking, they skip it.
- The Efficiency: One person (Single Thread) keeps 50 games moving because the "waiting" is done by the opponents (I/O), not the Grandmaster.
2. The Syntax: `async` and `await`
Asyncio introduces a new syntax. A function defined with `async def` is a Coroutine. It doesn't run when you call it; it returns a coroutine object that must be scheduled.
import asyncio
# 1. Define a Coroutine
async def say_hello():
print("Hello...")
# 2. Pause execution here for 1 second (Yield control)
await asyncio.sleep(1)
print("...World!")
# 3. Running it (The classic entry point)
# asyncio.run() starts the Event Loop
asyncio.run(say_hello())3. Running Concurrently: `asyncio.gather`
The power comes when you run multiple things at once. `asyncio.gather` schedules multiple coroutines and waits for ALL of them to finish.
import asyncio
import time
async def brew_coffee():
print("☕ Brewing coffee...")
await asyncio.sleep(2) # Simulates I/O
return "Coffee Ready"
async def toast_bread():
print("🞠Toasting bread...")
await asyncio.sleep(2)
return "Toast Ready"
async def main():
start = time.time()
# Run both AT THE SAME TIME
results = await asyncio.gather(brew_coffee(), toast_bread())
duration = time.time() - start
print(f"Breakfast: {results}")
print(f"Total time: {duration:.2f}s")
# Output: 2.01s (Not 4s!)
asyncio.run(main())4. Deep Dive: The Event Loop & Blocking
The Event Loop is a `while True` loop that runs tasks.CRITICAL RULE: Never do heavy math or `time.sleep()` inside an async function. It pauses the ENTIRE loop.
# ⌠BAD
async def bad_task():
time.sleep(10) # FREEZES the whole program for 10s!
# No other request can be handled.
# ✅ GOOD
async def good_task():
await asyncio.sleep(10) # Yields control.
# Other tasks run for 10s.5. Task Scheduling: `create_task`
Sometimes you want "Fire and Forget" mechanics. `create_task` submits a coroutine to the loop immediately, allowing the code to proceed without waiting for it.
async def background_logger():
# Runs in the background
await asyncio.sleep(1)
print("Log saved!")
async def main():
task = asyncio.create_task(background_logger()) # Starts NOW
print("Doing work...")
await asyncio.sleep(0.5)
await task # Wait for it eventually