PYTHON ADVANCED: PROFESSIONAL ENGINEERING MASTERY / L08ASYNC/AWAIT FUNDAMENTALS: NON-BLOCKING PYTHON
УРОКИ · 12 · 08 / 12
LESSON 08 · ADVANCED · 60 MIN · ◆ 3 INSTRUMENTS

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.

TIP

Learning 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.

FIG. 02Event Loop Visualizer
INTERACTIVE
LOADING INSTRUMENT
Fig. 02Watch asyncio schedule three awaiting coroutines — see why gather() finishes in the time of the longest task, not the sum.

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

FIG. 04Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 04Interactive Python code execution environment

Async Solution Preview

Async/await gives you concurrent I/O without threads, built from four moving parts:

PartRole
Event loopManages and schedules all async operations
CoroutinesFunctions defined with async def
awaitPauses the current coroutine and lets others run
TasksCoroutines 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

FIG. 06Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 06Interactive Python code execution environment

await Keyword

await pauses the coroutine until the awaited operation completes. During the pause, other coroutines can run.

What can be awaited:

AwaitableExample
Coroutineresult = await some_coroutine()
Tasktask = asyncio.create_task(some_coroutine()); result = await task
Futurefuture = asyncio.Future(); result = await future
Any __await__ objectCustom awaitable objects

What happens during await:

  1. The current coroutine suspends.
  2. Control returns to the event loop.
  3. The event loop runs other ready coroutines.
  4. 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

FIG. 08Flow Diagram
DIAGRAM
LOADING INSTRUMENT
Fig. 08Flow diagrams, timelines, and process visualizations

asyncio.gather - Run Multiple Coroutines

FIG. 10Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 10Interactive Python code execution environment

asyncio.create_task - Background Tasks

FIG. 12Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 12Interactive Python code execution environment

asyncio.wait - Advanced Control

FIG. 14Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 14Interactive Python code execution environment

asyncio.as_completed - Process as They Finish

FIG. 16Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 16Interactive Python code execution environment

Error Handling

FIG. 18Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 18Interactive Python code execution environment

Timeouts and Cancellation

FIG. 20Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 20Interactive Python code execution environment

Async Context Managers and Iterators

FIG. 22Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 22Interactive Python code execution environment

Async Generators

FIG. 24Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 24Interactive Python code execution environment

Practical Patterns

Semaphore - Rate Limiting

FIG. 26Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 26Interactive Python code execution environment

Queue-based Worker Pool

FIG. 28Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 28Interactive Python code execution environment

Async HTTP Requests (aiohttp pattern)

FIG. 30Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 30Interactive Python code execution environment

Practice Exercises

Exercise 1: Async Retry with Backoff

FIG. 32Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 32Interactive Python code execution environment

Exercise 2: Async Resource Pool

FIG. 34Python Code Executor
INTERACTIVE
LOADING INSTRUMENT
Fig. 34Interactive Python code execution environment

Key Takeaways

ConceptDescription
CoroutineFunction defined with async def
awaitPause and wait for async operation
Event LoopManages all async operations
TaskScheduled coroutine for concurrent execution
gatherRun multiple coroutines concurrently
SemaphoreLimit concurrent operations
QueueAsync producer-consumer pattern

When to Use Async

ScenarioUse Async?
Many network requestsYes
Database queriesYes (with async driver)
File I/OMaybe (use aiofiles)
CPU-heavy workNo (use multiprocessing)
Real-time eventsYes
Simple scriptsUsually 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

Foundational Talks & Articles

Modern Async Stack

  • asyncio.TaskGroup — Python 3.11's structured concurrency primitive. Use this instead of bare gather.
  • trio — alternative async framework with first-class structured concurrency.
  • anyio — write code that runs on both asyncio and trio. The async library library.
  • httpx — sync + async HTTP. The modern requests.
  • 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.
СВЯЗАННЫЕ ПОНЯТИЯ
asyncioasync-awaitevent-loopcoroutines