async/await allows Python code to handle concurrent operations efficiently, particularly for I/O-bound tasks like network requests and file operations.
Core Concepts
Event Loop
The event loop is the central coordinator that manages and schedules async tasks:asyncio.run() which handles the event loop for you:
Coroutines
Coroutines are functions defined withasync def:
Getting Started
import asyncio
async def fetch_data():
print("Start fetching")
await asyncio.sleep(2) # Simulate I/O operation
print("Done fetching")
return {"data": 42}
# Method 1: Using asyncio.run() (recommended)
result = asyncio.run(fetch_data())
# Method 2: In an existing async context
async def main():
result = await fetch_data()
print(result)
asyncio.run(main())
import asyncio
async def task_one():
await asyncio.sleep(1)
return "Task one complete"
async def task_two():
await asyncio.sleep(2)
return "Task two complete"
async def main():
# Run concurrently
results = await asyncio.gather(
task_one(),
task_two()
)
print(results)
# ['Task one complete', 'Task two complete']
asyncio.run(main())
The await Keyword
Awaiting Tasks
await pauses the current coroutine and lets the event loop run other tasks:
Awaiting Coroutines vs Tasks
Tasks
Creating Tasks
Tasks wrap coroutines and schedule them for execution:Task Management
Waiting Strategies
asyncio.gather()
Run multiple coroutines concurrently, wait for all:asyncio.wait()
More control over completion:asyncio.wait_for()
Set a timeout:Async Context Managers
Useasync with for resources that need async setup/cleanup:
Async Iterators
Useasync for to iterate over async data sources:
Error Handling
Try-Except with Async
Gathering with Exceptions
Real-World Examples
Concurrent HTTP Requests
Async Database Operations
Producer-Consumer Pattern
Custom Async Sleep
Understanding how async operations work internally:Best Practices
When to use async/await:
- ✅ I/O-bound operations (network requests, file I/O, database queries)
- ✅ Handling many concurrent connections
- ✅ Web servers and APIs
- ✅ Websockets and real-time applications
- ❌ CPU-bound tasks (use multiprocessing instead)
- ❌ Simple scripts (adds unnecessary complexity)
- ❌ Blocking libraries (use asyncio-compatible alternatives)
Debugging
Enable Debug Mode
- Log slow coroutines (>100ms)
- Warn about unawaited coroutines
- Track task creation locations
Check Running Tasks
Summary
Key takeaways:- Use
async defto create coroutines - Use
awaitto call async functions and yield control - Create tasks with
asyncio.create_task()for concurrency - Use
asyncio.gather()to wait for multiple tasks - Always await coroutines or create tasks from them
- Never use blocking operations in async code
- Use
asyncio.run()to start your async program
