Understanding ASGI

Difficulty: Beginner

ASGI (Asynchronous Server Gateway Interface) is the spiritual successor to WSGI (Web Server Gateway Interface), and understanding it is key to grasping why FastAPI performs so well. ASGI is a specification that defines how Python web applications communicate with web servers, with first-class support for asynchronous operations.

WSGI was the original standard for Python web applications, created in 2003 via PEP 3333. It works well for synchronous applications - frameworks like Flask and Django are built on it. In the WSGI model, each request occupies one thread for its entire duration. If a request waits for a database query or external API call, that thread is blocked and cannot serve other requests. To handle many simultaneous requests, WSGI servers spawn many threads or processes, which consumes significant memory.

ASGI was introduced to solve these limitations. It supports asynchronous request handling, meaning a single thread can handle multiple requests concurrently. When one request is waiting for I/O (database, HTTP call, file read), the event loop can switch to processing another request. This makes ASGI applications much more efficient for I/O-bound workloads, which is exactly what most web APIs are. ASGI also natively supports WebSockets and HTTP/2, which WSGI cannot handle.

Uvicorn is the most popular ASGI server for Python. It is built on uvloop (a fast event loop implementation written in Cython, replacing the default asyncio event loop) and httptools (a fast HTTP parser written in C). This combination makes Uvicorn extremely fast. When you run uvicorn main:app, Uvicorn creates an event loop, loads your FastAPI application, and begins accepting HTTP connections. Each incoming request is dispatched to the appropriate route handler.

The event loop is the core of asynchronous programming. It continuously checks for events (incoming requests, completed I/O operations) and dispatches handlers. In Python, this is built on the asyncio module. When you write async def and use await, you are cooperating with the event loop: await tells the loop 'I am waiting for something, go handle other tasks.' This cooperative multitasking is much lighter than thread-based concurrency.

FastAPI handles sync and async handlers differently. When you use async def, the handler runs directly on the event loop. When you use def (without async), FastAPI runs the handler in a thread pool (using run_in_executor) to prevent blocking the event loop. This means you can safely use blocking libraries in sync handlers, but for best performance with I/O-heavy operations, prefer async handlers with async libraries.

For production deployments, Uvicorn is often run behind Gunicorn using the UvicornWorker. This gives you Gunicorn's process management (automatic worker restarts, graceful shutdowns) combined with Uvicorn's ASGI performance. The command is: gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker, where -w 4 creates 4 worker processes.

Code examples

ASGI vs WSGI Comparison

# WSGI application (synchronous)
# Each request blocks a thread while waiting for I/O
def wsgi_app(environ, start_response):
    # This blocks the thread while waiting
    data = fetch_from_database()  # Blocking call
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [data.encode()]

# ASGI application (asynchronous)
# Requests can yield control while waiting for I/O
async def asgi_app(scope, receive, send):
    if scope['type'] == 'http':
        # This yields to the event loop while waiting
        data = await async_fetch_from_database()  # Non-blocking
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [[b'content-type', b'text/plain']],
        })
        await send({
            'type': 'http.response.body',
            'body': data.encode(),
        })

WSGI is a synchronous interface - each request blocks a thread. ASGI is asynchronous - the event loop can handle other requests while one waits for I/O. FastAPI abstracts this low-level ASGI protocol into the clean decorator-based API you use.

Concurrency Demonstration

from fastapi import FastAPI
import asyncio
import time

app = FastAPI()

# Async endpoint: handles 10 concurrent requests in ~1 second
@app.get("/async-slow")
async def async_slow():
    await asyncio.sleep(1)  # Non-blocking sleep
    return {"handler": "async", "waited": "1 second"}

# Sync endpoint: handles 10 concurrent requests in ~1 second
# (because FastAPI runs sync handlers in a thread pool)
@app.get("/sync-slow")
def sync_slow():
    time.sleep(1)  # Blocking sleep, but runs in thread pool
    return {"handler": "sync", "waited": "1 second"}

# WRONG: async handler with blocking sleep
# This blocks the entire event loop!
@app.get("/broken")
async def broken_endpoint():
    time.sleep(1)  # BLOCKS the event loop!
    return {"handler": "broken"}

The async endpoint uses asyncio.sleep which yields control to the event loop. The sync endpoint uses time.sleep but FastAPI runs it in a thread pool. The 'broken' endpoint uses time.sleep inside an async function, which blocks the event loop and prevents all other requests from being processed.

Production Server Configuration

# Development: single worker with auto-reload
uvicorn main:app --reload

# Production: Uvicorn with multiple workers
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

# Production: Gunicorn with Uvicorn workers (recommended)
gunicorn main:app \
    --bind 0.0.0.0:8000 \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --access-logfile - \
    --error-logfile -

# Number of workers rule of thumb:
# workers = (2 * CPU_CORES) + 1
# For a 4-core machine: 9 workers

# Programmatic Uvicorn (in Python)
import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        reload=True,  # Development only
    )

Development uses --reload for convenience. Production uses multiple workers for concurrency. Gunicorn with UvicornWorker is the recommended production setup as it provides process management, graceful restarts, and signal handling on top of Uvicorn's ASGI performance.

Key points

Concepts covered

ASGI, WSGI, Uvicorn, Event Loop, Concurrency