Skip to main content
Asynchronous programming with 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:
In practice, use asyncio.run() which handles the event loop for you:

Coroutines

Coroutines are functions defined with async def:

Getting Started

The await Keyword

Awaiting Tasks

await pauses the current coroutine and lets the event loop run other tasks:
Output:

Awaiting Coroutines vs Tasks

Important distinction:

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

Use async with for resources that need async setup/cleanup:

Async Iterators

Use async 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)
Common pitfalls:
  1. Blocking the event loop:
  2. Forgetting to await:
  3. Not creating tasks for concurrency:

Debugging

Enable Debug Mode

Debug mode will:
  • Log slow coroutines (>100ms)
  • Warn about unawaited coroutines
  • Track task creation locations

Check Running Tasks

Summary

Key takeaways:
  1. Use async def to create coroutines
  2. Use await to call async functions and yield control
  3. Create tasks with asyncio.create_task() for concurrency
  4. Use asyncio.gather() to wait for multiple tasks
  5. Always await coroutines or create tasks from them
  6. Never use blocking operations in async code
  7. Use asyncio.run() to start your async program