Asyncio Introduction¶
What is Asyncio?¶
asyncio is Python's built-in library for writing concurrent code using the async/await syntax. It provides a framework for managing asynchronous I/O operations, enabling thousands of concurrent connections with a single thread.
import asyncio
async def main():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(main())
Concurrency Models Comparison¶
| Model | Library | Best For | Parallelism |
|---|---|---|---|
| Threading | threading |
I/O-bound (simple) | Limited by GIL |
| Multiprocessing | multiprocessing |
CPU-bound | True parallelism |
| Asyncio | asyncio |
I/O-bound (many connections) | Single-threaded concurrency |
When to Use Asyncio¶
✅ Good Use Cases¶
# Network requests (HTTP clients)
async def fetch_all_urls(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
# Web servers (FastAPI, aiohttp)
# Database queries (asyncpg, motor)
# WebSocket connections
# File I/O (aiofiles)
Asyncio excels when: - Many concurrent I/O operations - High number of connections (1000s) - Network-bound applications - Real-time applications (chat, streaming)
❌ Poor Use Cases¶
# CPU-bound work - use multiprocessing instead
def compute_heavy():
return sum(i * i for i in range(10_000_000))
# Simple scripts with few I/O operations
# Code that needs true parallelism
Asyncio vs Threading¶
Threading Approach¶
import threading
import time
def fetch(url):
time.sleep(1) # Simulate I/O
return f"Result from {url}"
# 10 threads for 10 URLs
threads = []
for url in urls:
t = threading.Thread(target=fetch, args=(url,))
threads.append(t)
t.start()
for t in threads:
t.join()
Problems: - Thread overhead (memory, context switching) - Limited scalability (100s of threads, not 1000s) - Race conditions with shared state
Asyncio Approach¶
import asyncio
async def fetch(url):
await asyncio.sleep(1) # Simulate I/O
return f"Result from {url}"
async def main():
tasks = [fetch(url) for url in urls]
results = await asyncio.gather(*tasks)
return results
asyncio.run(main())
Benefits:
- Single thread, no race conditions
- Minimal overhead per task
- Scales to 10,000s of concurrent operations
- Explicit yield points (await)
Key Concepts Overview¶
1. Coroutines¶
Functions defined with async def:
async def my_coroutine():
return "Hello"
# Calling returns a coroutine object, not the result
coro = my_coroutine() # <coroutine object>
# Must be awaited or run
result = asyncio.run(my_coroutine()) # "Hello"
2. await Keyword¶
Suspends coroutine until the awaited operation completes:
async def fetch_data():
result = await some_async_operation() # Pause here
return result # Resume when done
3. Event Loop¶
The central scheduler that runs coroutines:
# Modern way (Python 3.7+)
asyncio.run(main())
# Manual control (rarely needed)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
4. Tasks¶
Wrap coroutines for concurrent execution:
async def main():
# Create tasks for concurrent execution
task1 = asyncio.create_task(fetch("url1"))
task2 = asyncio.create_task(fetch("url2"))
# Both run concurrently
result1 = await task1
result2 = await task2
Minimal Working Example¶
import asyncio
async def say_after(delay, message):
"""Coroutine that waits then prints."""
await asyncio.sleep(delay)
print(message)
async def main():
print(f"Started at {asyncio.get_event_loop().time():.2f}")
# Sequential (3 seconds total)
# await say_after(1, "Hello")
# await say_after(2, "World")
# Concurrent (2 seconds total)
await asyncio.gather(
say_after(1, "Hello"),
say_after(2, "World")
)
print(f"Finished at {asyncio.get_event_loop().time():.2f}")
asyncio.run(main())
Output:
Started at 0.00
Hello
World
Finished at 2.00
Common Async Libraries¶
| Category | Library | Description |
|---|---|---|
| HTTP Client | aiohttp, httpx |
Async HTTP requests |
| Web Framework | FastAPI, Starlette |
Async web servers |
| Database | asyncpg, motor, aiosqlite |
Async database drivers |
| File I/O | aiofiles |
Async file operations |
| Redis | aioredis |
Async Redis client |
Summary¶
| Concept | Description |
|---|---|
asyncio |
Python's async I/O framework |
async def |
Defines a coroutine |
await |
Suspends until operation completes |
| Event loop | Scheduler that runs coroutines |
| Best for | High-concurrency I/O-bound tasks |
Key Takeaways:
- Asyncio provides single-threaded concurrency
- Use for I/O-bound tasks with many concurrent operations
async/awaitmakes async code readable- Not a replacement for multiprocessing (CPU-bound work)
- Modern Python async ecosystem is mature and widely adopted