Django 3.0, out on 2 December 2019, can be served as an ASGI application alongside the usual WSGI, but views, middleware, the ORM and the cache all stay entirely synchronous. The change touches the outermost layer of the request-handling chain, not the application code you write every day.
Context
ASGI (Asynchronous Server Gateway Interface) is the specification that extends WSGI (Web Server Gateway Interface) to the protocols WSGI could not describe: long-lived connections, WebSocket, server-sent events, and in general any exchange that does not reduce to a single request/response pair on one thread. The specification was already in circulation outside the Django core thanks to the Channels project, which since 2016 had given a path for WebSocket and background tasks; with 3.0 the interface enters the framework.
The technical reason is I/O-bound load. A view waiting on an external service with variable latency keeps a worker busy for the whole wait, even when the process is doing nothing. Under WSGI, concurrency comes from multiplying threads or processes, and you pay for it in memory and context switches. The single event-loop model of asyncio, in the standard library since 3.4 and with the async/await syntax since 3.5, holds many waiting connections on a single thread.
What Django 3.0 really does
The project generated by startproject now carries an asgi.py next to wsgi.py. It points at get_asgi_application(), which returns an instance of ASGIHandler. This handler speaks the ASGI protocol towards the server — Daphne or Uvicorn, say — and internally translates each request into the same synchronous cycle.
The translation goes through asgiref, the utility library that accompanies the specification. At its centre are two functions:
sync_to_asyncwraps a synchronous callable so it can be awaited from asynchronous code, running it in a thread pool.async_to_syncdoes the opposite, running a coroutine from a synchronous context until it finishes.
ASGIHandler receives the event from the loop asynchronously, builds the HttpRequest object, then hands URL resolution, middleware and the view to the same synchronous code as 2.x, wrapped in sync_to_async. The response travels back the same way. On its own, this arrangement gains little: the body of the view still runs on a pool thread, so a traditional view served via ASGI does not get faster, and at times pays a small overhead compared with WSGI.
The critical point
3.0 is worth it for what you can build on top of the ASGI handler; existing code does not run any faster. With Django under an ASGI server, within the same application you mount code that speaks the protocol natively: routing that tells the http and websocket scopes apart, long-lived handlers, integration with Channels without a separate process. The classic HTTP request keeps going through the synchronous path; the new connection types, by contrast, finally find a place inside the framework.
There is a compatibility constraint worth stating plainly. The Django 3.0 documentation warns: if from an asynchronous context you call synchronous code that in turn touches the database, the ORM’s guarantees collapse, built as they are on the assumption of one thread per request. This is why the handler isolates synchronous execution in dedicated threads instead of running it on the loop. Skip that boundary — for instance by firing a query with async_to_sync from a shared coroutine — and you collect errors that are hard to diagnose. The boundary between the two worlds is crossed only by going through asgiref, never by hand.
The roadmap behind the choice
Nothing improvised here: the opening comes from DEP 0009 (“Async-capable Django”), where DEP stands for Django Enhancement Proposal — Andrew Godwin’s proposal approved by the Technical Board in July 2019. In its “Sequencing” section the document plans a first round (hopefully 3.0) that groups together HTTP handling, middleware and views in native async form with a synchronous wrapper, and defers the deeper pieces — ORM, cache, authentication — to later steps. The stated principle is to introduce async at first by running synchronous code in threads, and to replace it with native implementations only where the benefit is concrete, without ever breaking applications already written.
It is worth separating what the DEP planned from what 3.0 eventually brought home. The document’s first round already included async views and middleware, but the release stopped at the outer ASGI handler and deferred them to the next version. In 3.0, therefore, declaring a view with async def produces no asynchronous behaviour: the framework still runs it on the synchronous path. The ORM, the hardest piece to convert because QuerySet, managers and relations rest on blocking operations, stays synchronous and sits among the deeper pieces in the DEP, with no fixed date.
Practical implications
For an existing Django codebase, moving to 3.0 does not force you to adopt ASGI. WSGI stays supported and is still the right choice for an application that serves only traditional HTTP requests: pulling in an ASGI server to run entirely synchronous code adds complexity without a measurable gain.
Moving to ASGI makes sense when there is a concrete requirement WSGI does not cover — WebSocket, streaming, Channels integration in the same process — or to be ready for when async views and middleware arrive in the framework. It is worth checking that the chosen server is compatible with the version of ASGI Django 3.0 uses, and that any third-party libraries on the request path do not implicitly assume WSGI.
Limits
As of April 2020, async in Django stops at the outer handler: everything inside the request is still synchronous. Throughput measurements on real workloads should be taken against your own application, because serving synchronous code via ASGI can come out slower than via WSGI, due to the thread-pool crossing. The later parts of the DEP have no guaranteed release date, and a calendar estimate on a volunteer-driven open source project would be unreliable. Anyone weighing the migration should count on what is documented for 3.0, not on what the roadmap promises for the future.
- https://docs.djangoproject.com/en/3.0/releases/3.0/
- https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
- https://asgi.readthedocs.io/en/latest/
- https://github.com/django/asgiref
- https://github.com/django/deps/blob/main/accepted/0009-async.rst
- https://channels.readthedocs.io/en/latest/
- https://docs.python.org/3/library/asyncio.html
- https://www.djangoproject.com/weblog/2019/dec/02/django-30-released/
- https://www.noze.it/en/insights/django-async/
Cover image: Browser screenshot of the default Django 3.0 welcome page: a green rocket and the message “The install worked successfully!” — screenshot by Ferbinu, CC BY-SA 4.0 — https://commons.wikimedia.org/wiki/File:Django_landing_page_3.0.png