УРОКИ · 12 · 08 / 12
Async/Await Fundamentals: Non-Blocking Python
Understand event loops and coroutines. Write async code with asyncio, handle concurrent I/O, and build async patterns for production.
TIPLearning Objectives: After this lesson, you'll understand event loops and coroutines, write async code with asyncio, handle concurrent I/O operations, and build async patterns for production applications.
Understanding Async Programming
Async programming handles many I/O operations concurrently on a single thread — no extra threads, no parallelism. The trick is the event loop. Run it below: switch between gather() (concurrent) and one-by-one await (sequential), nudge a task's sleep longer, and watch how the loop schedules three coroutines — and why concurrent finishes in the time of the longest one, not the sum:
TIP▶ Try this first. Open the EventLoopVisualizer below and compare the concurrent run against the sequential one: watch whether the coroutines overlap while one is paused at an
await, and notice that the concurrent total tracks the longest task rather than the sum of all three. Come back to the theory once you've seen it move.
The event loop runs coroutines one at a time. When one hits await, it pauses and another can run. This is cooperative multitasking.
The Problem with Blocking I/O
Async Solution Preview
Async/await gives you concurrent I/O without threads, built from four moving parts:
| Part | Role |
|---|---|
| Event loop | Manages and schedules all async operations |
| Coroutines | Functions defined with async def |
await | Pauses the current coroutine and lets others run |
| Tasks | Coroutines scheduled for concurrent execution |
The payoff is in the timeline. Synchronous requests run back to back, so three 1s requests take ~3s. Async requests overlap while each one is paused at an await, so the same three finish in ~1s — the time of the longest one:
Synchronous: |--Request 1--|--Request 2--|--Request 3--| (~3s total) Async: |--Request 1--| |--Request 2--| (overlapping!) |--Request 3--| (~1s total)
Here's the pattern you'll build toward — the real, runnable asyncio version follows below:
async def fetch_data(url): print(f"Fetching {url}...") await asyncio.sleep(0.3) # Non-blocking wait return f"Data from {url}" async def main(): # Run all fetches concurrently tasks = [ fetch_data("http://api.example.com/1"), fetch_data("http://api.example.com/2"), fetch_data("http://api.example.com/3"), ] results = await asyncio.gather(*tasks) return results # Run the async code results = asyncio.run(main())
Coroutines and async def
await Keyword
await pauses the coroutine until the awaited operation completes. During the pause, other coroutines can run.
What can be awaited:
| Awaitable | Example |
|---|---|
| Coroutine | result = await some_coroutine() |
| Task | task = asyncio.create_task(some_coroutine()); result = await task |
| Future | future = asyncio.Future(); result = await future |
Any __await__ object | Custom awaitable objects |
What happens during await:
- The current coroutine suspends.
- Control returns to the event loop.
- The event loop runs other ready coroutines.
- When the awaited operation completes, the coroutine resumes.
A sequential example — each await waits for the previous one to finish before continuing:
async def fetch_user(user_id): # Simulate database query await asyncio.sleep(0.1) return {"id": user_id, "name": f"User {user_id}"} async def fetch_posts(user_id): # Simulate API call await asyncio.sleep(0.2) return [f"Post {i} by user {user_id}" for i in range(3)] async def get_user_with_posts(user_id): # These run sequentially (each await waits) user = await fetch_user(user_id) posts = await fetch_posts(user_id) return {"user": user, "posts": posts} async def main(): result = await get_user_with_posts(123) print(result) asyncio.run(main())
Concurrent Execution
asyncio.gather - Run Multiple Coroutines
asyncio.create_task - Background Tasks
asyncio.wait - Advanced Control
asyncio.as_completed - Process as They Finish
Error Handling
Timeouts and Cancellation
Async Context Managers and Iterators
Async Generators
Practical Patterns
Semaphore - Rate Limiting
Queue-based Worker Pool
Async HTTP Requests (aiohttp pattern)
Practice Exercises
Exercise 1: Async Retry with Backoff
Exercise 2: Async Resource Pool
Key Takeaways
| Concept | Description |
|---|---|
| Coroutine | Function defined with async def |
| await | Pause and wait for async operation |
| Event Loop | Manages all async operations |
| Task | Scheduled coroutine for concurrent execution |
| gather | Run multiple coroutines concurrently |
| Semaphore | Limit concurrent operations |
| Queue | Async producer-consumer pattern |
When to Use Async
| Scenario | Use Async? |
|---|---|
| Many network requests | Yes |
| Database queries | Yes (with async driver) |
| File I/O | Maybe (use aiofiles) |
| CPU-heavy work | No (use multiprocessing) |
| Real-time events | Yes |
| Simple scripts | Usually overkill |
Next Steps
In the next lesson, we'll explore Testing Best Practices—master pytest with fixtures, parametrization, and mocking, learn TDD, and write maintainable tests.
Ready to write bulletproof code? Testing awaits!
Further Reading
Required Reading
asyncio— official Python docs — start here.- Real Python — Async IO in Python — the canonical full walkthrough.
- FastAPI — Concurrency and async/await — the clearest practical explanation of when to use
async. Required reading before you write your first async API.
Foundational Talks & Articles
- David Beazley — Build Your Own Async — builds an async framework from scratch on stage. Best way to understand event loops.
- Nathaniel Smith — Notes on structured concurrency — the article that inspired Python's
TaskGroup(3.11+) and thetriolibrary. - Łukasz Langa — Async Pitfalls — what to avoid in production async code.
Modern Async Stack
asyncio.TaskGroup— Python 3.11's structured concurrency primitive. Use this instead of baregather.trio— alternative async framework with first-class structured concurrency.anyio— write code that runs on bothasyncioandtrio. The async library library.httpx— sync + async HTTP. The modernrequests.aiohttp— battle-tested async HTTP server + client.asyncpg— fastest async PostgreSQL driver in any language.
Books
- Book: Fluent Python (2nd ed.) — Chapters 19 ("Concurrency Models") and 21 ("Asynchronous Programming"). Best treatment in print.
- Book: Python Concurrency with asyncio — Matthew Fowler. Whole book on the topic.