1.async def views, await, async middleware, and the ASGI requirement
Writing `async def` in front of a view makes it a coroutine, which lets you `await` inside it. It does not, on its own, make anything faster. Under WSGI, Django runs each async view in its own one-off event loop — you can still `await` concurrent HTTP calls, but you get none of the concurrency benefit of an async stack and you pay roughly a millisecond of context-switching per request. Real concurrency needs ASGI, and the Django docs add a condition people miss: you only get a fully asynchronous stack if **no synchronous middleware is loaded**, because one sync middleware forces a thread switch for every request and can erase the advantage entirely.
Key features
- Under WSGI, Django runs an async view in its own one-off event loop — it works, but there is no concurrency benefit and about a millisecond of context-switching cost
- The docs are explicit that a fully asynchronous stack requires **no synchronous middleware** loaded; one sync middleware forces a thread switch for every request
- Middleware declares `sync_capable` and `async_capable` class attributes; Django adapts at each mismatch, and logs "Asynchronous handler adapted for middleware …" at debug level
- A blocking call inside an `async def` view stalls the whole event loop, not just that request — the cost is paid by every other connection on that worker
- Async views shine on I/O you actually await concurrently — several upstream HTTP calls with `asyncio.gather()` — and do nothing at all for CPU-bound work
- Switching a project to ASGI is a deployment change (`ASGI_APPLICATION`, an ASGI server) plus a middleware audit, not just a keyword in front of a view
What `async def` actually buys, by deployment
- Request
- leads to Which server?
- Which server? — WSGI or ASGI — decided at deploy, not in the view
- on error, leads to WSGI: one-off event loop per request (WSGI)
- leads to ASGI: the middleware chain (ASGI)
- WSGI: one-off event loop per request — the view runs; the concurrency does not
- leads to Overhead, and nothing else
- ASGI: the middleware chain — every sync middleware forces a thread switch
- leads to Any synchronous middleware?
- Any synchronous middleware? — one is enough to erase the gain
- on error, leads to Overhead, and nothing else (yes)
- leads to async def view (all async-capable)
- async def view — a promise not to block, which Python cannot enforce
- on error, leads to A blocking call inside it (blocking driver / CPU work)
- leads to await on real async I/O (genuinely awaitable I/O)
- A blocking call inside it — stalls the loop for every other connection on this worker
- leads to Overhead, and nothing else
- await on real async I/O — asyncio.gather over several upstream calls
- leads to Concurrency actually gained
- Concurrency actually gained
- Overhead, and nothing else
Command
async def dashboard(request):
prices, weather = await asyncio.gather(fetch_prices(), fetch_weather())
return JsonResponse({"prices": prices, "weather": weather})`async def` describes how a view *waits*; it makes nothing faster by itself. Under WSGI each async view gets a one-off event loop, so there is no concurrency gain and about a millisecond of overhead. Under ASGI the gain is real, but only if no synchronous middleware is loaded — one is enough to force a thread switch per request — and only if every I/O call inside is genuinely awaitable, because one blocking call stalls the event loop for every other connection on that worker.