Concurrency: Mastering Async and Await

Concurrency: Mastering Async and Await

Unlock the full power of your server. Learn how to optimize FastAPI's event loop to handle thousands of concurrent connections with minimal hardware.

Concurrency: Mastering Async and Await

FastAPI is famous for its speed, but that speed doesn't come for free. To get the most out of it, you must understand how Asynchronous Programming works in Python.

In this lesson, we learn how to keep the "Event Loop" moving to handle massive traffic.


1. The Event Loop Metaphor

Imagine a Chef (the CPU) in a kitchen.

  • Synchronous: The chef puts a steak on the grill and stands there watching it for 10 minutes, doing nothing else while it cooks.
  • Asynchronous: The chef puts a steak on the grill, sets a timer (awaits), and immediately turns around to chop vegetables or wash dishes. When the timer goes off, the chef returns to the steak.

FastAPI is the Chef. The await keyword is the timer.


2. When to use async def vs def

FastAPI is unique because it supports both.

  • Use async def: When you are doing I/O work (Database calls, API calls, file reading). This tells FastAPI: "I'm going to wait for a bit, go handle other users while I'm gone."
  • Use def: For CPU-heavy work (Calculating math, processing images). FastAPI will run these in a separate threadpool so they don't block the main event loop.

3. The Danger: Blocking the Loop

If you use a Sync library (like requests or psycopg2) inside an Async function, you "Freeze" the kitchen.

# DON'T DO THIS
@app.get("/slow")
async def slow_route():
    import time
    time.sleep(5) # The entire server is frozen for 5 seconds!
    return {"msg": "done"}

# DO THIS INSTEAD
import asyncio
@app.get("/fast")
async def fast_route():
    await asyncio.sleep(5) # The server handles 1,000 other users while waiting
    return {"msg": "done"}

4. Concurrency != Parallelism

  • Concurrency: Handling many things at once (Interleaving).
  • Parallelism: Doing many things at once (using multiple CPU cores). FastAPI achieves high concurrency with one core, but you use tools like Gunicorn to achieve parallelism by running multiple copies of FastAPI.

Visualizing the Event Loop

graph TD
    A["User 1 Request"] --> B["Event Loop"]
    B --> C["DB Call (Await 100ms)"]
    B --> D["User 2 Request"]
    D --> E["Cache Call (Await 5ms)"]
    E --> F["Return User 2 Response"]
    B --> G["DB Returns Data"]
    G --> H["Return User 1 Response"]

Summary

  • await: The most important keyword in modern Python.
  • I/O Bound: Use async for things that involve waiting for a network or disk.
  • CPU Bound: Use standard def for things that involve heavy calculation.
  • Non-Blocking: Never use time.sleep() in an async def function.

In the next lesson, we’ll look at Caching with Redis, the ultimate speed booster for any API.


Exercise: The Loop Blocker

You are using a library to generate QR codes. The library is synchronous and takes 200ms of CPU time. Should you define your FastAPI route as async def or def? Why?

Subscribe to our newsletter

Get the latest posts delivered right to your inbox.

Subscribe on LinkedIn