Asynkront stöd¶
Django has support for writing asynchronous (”async”) views, along with an entirely async-enabled request stack if you are running under ASGI. Async views will still work under WSGI, but with a small per-request adaptation cost (see Prestanda), and without the ability to have efficient long-running requests.
Many parts of Django provide asynchronous APIs, including the ORM, the cache framework, authentication, sessions, and signals.
For other code, the sync_to_async() adapter is a low-cost bridge (see
Prestanda). A wide range of async-native Python libraries can
also be integrated.
Asynkrona vyer¶
Alla vyer kan förklaras asynkrona genom att den anropbara delen av den returnerar en coroutine - vanligtvis görs detta med hjälp av async def. För en funktionsbaserad vy innebär detta att hela vyn deklareras med async def. För en klassbaserad vy innebär det att HTTP-metodhanterarna, till exempel get() och post(), deklareras som async def (inte dess __init__() eller as_view()).
Observera
Django använder asgiref.sync.iscoroutinefunction för att testa om din vy är asynkron eller inte. Om du implementerar din egen metod för att returnera en coroutine, se till att du använder asgiref.sync.markcoroutinefunction så att denna funktion returnerar True.
Under en WSGI-server kommer asynkrona vyer att köras i sin egen engångshändelseslinga. Detta innebär att du kan använda asynkrona funktioner, som samtidiga asynkrona HTTP-förfrågningar, utan problem, men du får inte fördelarna med en asynkron stack.
De främsta fördelarna är möjligheten att hantera hundratals anslutningar utan att använda Python-trådar. Detta gör att du kan använda långsam streaming, long-polling och andra spännande svarstyper.
Om du vill använda dessa måste du distribuera Django med hjälp av ASGI istället.
Observera
A fully asynchronous request stack requires async middleware end-to-end. Where a piece of synchronous middleware sits between an ASGI server and an async view, Django adapts it by running it in its own thread; see Prestanda for the cost trade-off.
Django’s bundled middleware supports both sync and async. Third-party middleware may not. To see which
middleware Django adapts, turn on debug logging for the django.request
logger and look for log messages about ”Asynchronous handler adapted for
middleware …”.
I både ASGI- och WSGI-läge kan du fortfarande på ett säkert sätt använda asynkront stöd för att köra kod samtidigt i stället för seriellt. Detta är särskilt praktiskt när du hanterar externa API:er eller datalager.
Om du vill anropa en del av Django som fortfarande är synkron, måste du linda in den i ett sync_to_async()-anrop. Till exempel:
from asgiref.sync import sync_to_async
results = await sync_to_async(sync_function, thread_sensitive=True)(pk=123)
Om du av misstag försöker anropa en del av Django som endast är synkron från en asynkron vy, kommer du att utlösa Djangos asynkrona säkerhetsskydd för att skydda dina data från korruption.
Dekoratörer¶
Följande dekoratorer kan användas med både synkrona och asynkrona vyfunktioner:
villkorlig_sida()xframe_options_deny()xframe_options_sameorigin()xframe_options_exempt()
Till exempel:
from django.views.decorators.cache import never_cache
@never_cache
def my_sync_view(request): ...
@never_cache
async def my_async_view(request): ...
Frågor och ORM¶
With some exceptions, Django can run ORM queries asynchronously:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
Detaljerade anvisningar finns i Asynkrona förfrågningar, men i korthet:
Alla
QuerySet-metoder som orsakar en SQL-fråga har en asynkron variant meda-prefix.async forstöds på alla QuerySets (inklusive utdata frånvalues()ochvalues_list().)
Asynchronous model methods that use the database are also supported:
async def make_book(*args, **kwargs):
book = Book(...)
await book.asave(using="secondary")
async def make_book_with_tags(tags, *args, **kwargs):
book = await Book.objects.acreate(...)
await book.tags.aset(tags)
Transaktioner fungerar ännu inte i asynkront läge. Om du har en del av koden som behöver transaktionsbeteende rekommenderar vi att du skriver den delen som en enda synkron funktion och anropar den med sync_to_async().
Persistent database connections, set
via the CONN_MAX_AGE setting, should also be disabled in async mode.
Instead, use your database backend’s built-in connection pooling if available,
or investigate a third-party connection pooling option if required. As in
synchronous Django, concurrent requests in a single process share that pool, so
size it to the target in-flight query concurrency.
Prestanda¶
When running in a mode that does not match the view (e.g. an async view under WSGI, or a traditional sync view under ASGI), Django must emulate the other call style to allow your code to run. The per-call cost of this adaptation is small: tens of microseconds in the in-request ASGI path, where the running event loop is reused, and a few hundred microseconds in the cold-start path used by management commands, background tasks, and scripts. Against typical request times measured in milliseconds, this is rarely visible in itself, but can become so under GIL contention as the number of active threads grows.
If you find yourself wrapping individual rows or operations in a tight loop,
restructure your code so the loop runs inside a single sync_to_async()
(or async_to_sync()) crossing. The per-call cost of the context switch is
then spread across the whole loop and effectively disappears.
The same per-call adaptation cost applies to middleware. Django will attempt to minimize the number of context-switches between sync and async. If you have an ASGI server, but all your middleware and views are synchronous, it will switch just once, before it enters the middleware stack.
However, if you put synchronous middleware between an ASGI server and an asynchronous view, it will have to switch into sync mode for the middleware and then back to async mode for the view. Django will also hold the sync thread open for middleware exception propagation. For request/response views that hit the ORM and return, this is not usually a meaningful penalty. It matters most when you are using ASGI for high in-process concurrency over non-ORM I/O (for example upstream HTTP fan-out, server-sent events, or other long-lived requests), where the extra thread per request caps that concurrency.
Du bör göra dina egna prestandatester för att se vilken effekt ASGI kontra WSGI har på din kod. I vissa fall kan det finnas en prestandaförbättring även för en rent synkron kodbas under ASGI eftersom all kod som hanterar förfrågningar fortfarande körs asynkront. I allmänhet vill du bara aktivera ASGI-läget om du har asynkron kod i ditt projekt.
Hantering av frånkopplingar¶
För långlivade förfrågningar kan en klient koppla från innan vyn returnerar ett svar. I detta fall kommer ett asyncio.CancelledError att uppstå i vyn. Du kan fånga detta fel och hantera det om du behöver utföra någon upprensning:
async def my_view(request):
try:
# Do some work
...
except asyncio.CancelledError:
# Handle disconnect
raise
Du kan också hantera klientavbrott i strömmande svar.
Asynkron säkerhet¶
- DJANGO_ALLOW_ASYNC_UNSAFE¶
Certain key parts of Django are not able to operate safely in an async environment, as they have global state that is not coroutine-aware. These parts of Django are classified as ”async-unsafe”, and are protected from execution in an async environment. The synchronous API of the ORM is the main example, but there are other parts that are also protected in this way.
Om du försöker köra någon av dessa delar från en tråd där det finns en löpande händelseslinga, kommer du att få ett SynchronousOnlyOperation-fel. Observera att du inte behöver vara inne i en async-funktion direkt för att detta fel ska uppstå. Om du har anropat en sync-funktion direkt från en async-funktion, utan att använda sync_to_async() eller liknande, så kan det också inträffa. Detta beror på att din kod fortfarande körs i en tråd med en aktiv händelseslinga, även om den kanske inte är deklarerad som asynkron kod.
Om du stöter på detta fel bör du korrigera din kod så att du inte anropar den felaktiga koden från ett async-sammanhang. Skriv istället din kod som pratar med async-osäkra funktioner i sin egen sync-funktion och anropa den med asgiref.sync.sync_to_async() (eller något annat sätt att köra sync-kod i sin egen tråd).
Async-kontexten kan påtvingas dig av den miljö där du kör din Django-kod. Till exempel tillhandahåller Jupyter-anteckningsböcker och IPython interaktiva skal båda transparent en aktiv händelseslinga så att det är lättare att interagera med asynkrona API: er.
Om du använder ett IPython-skal kan du inaktivera den här händelseslingan genom att köra:
%autoawait off
som ett kommando i IPython-prompten. Detta gör att du kan köra synkron kod utan att generera SynchronousOnlyOperation-fel; men du kommer inte heller att kunna await asynkrona API:er. För att slå på händelseslingan igen, kör:
%autoawait on
Om du befinner dig i en annan miljö än IPython (eller om du av någon anledning inte kan stänga av autoawait i IPython), du är säker på att det inte finns någon chans att din kod körs samtidigt och du absolut behöver köra din synkroniseringskod från en asynkron kontext, kan du inaktivera varningen genom att ställa in miljövariabeln DJANGO_ALLOW_ASYNC_UNSAFE till valfritt värde.
Varning
Om du aktiverar det här alternativet och det finns samtidig åtkomst till de asynkrona osäkra delarna av Django kan du drabbas av dataförlust eller korruption. Var mycket försiktig och använd inte detta i produktionsmiljöer.
Om du behöver göra detta från Python, gör det med os.environ:
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
Asynkrona adapterfunktioner¶
Det är nödvändigt att anpassa anropsstilen när man anropar sync-kod från en async-kontext, eller vice versa. För detta finns det två adapterfunktioner från modulen asgiref.sync: async_to_sync`() och sync_to_async`(). De används för att övergå mellan anropsstilarna samtidigt som kompatibiliteten bevaras.
Dessa adapterfunktioner används i stor utsträckning i Django. Själva paketet asgiref är en del av Django-projektet och det installeras automatiskt som ett beroende när du installerar Django med pip.
async_to_sync()¶
- async_to_sync(async_function, force_new_loop=False)¶
Tar en async-funktion och returnerar en sync-funktion som omsluter den. Kan användas som antingen en direkt omslutning eller en dekorator:
from asgiref.sync import async_to_sync
async def get_data(): ...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data(): ...
Async-funktionen körs i händelseslingan för den aktuella tråden, om en sådan finns. Om det inte finns någon aktuell händelseslinga startas en ny händelseslinga specifikt för den enskilda async-inkallningen och stängs av igen när den är klar. I båda situationerna kommer async-funktionen att köras i en annan tråd än den anropande koden.
Värdena för Threadlocals och contextvars bevaras över gränsen i båda riktningarna.
async_to_sync() is essentially a more powerful version of the
asyncio.run() function in Python’s standard library. As well as ensuring
threadlocals work, it also enables the thread_sensitive mode of
sync_to_async() when that wrapper is used below it. In the cold path
(no running event loop) it pays the cost of starting a fresh event loop, like
asyncio.run(); when an event loop is already running (the in-request ASGI
case), the running loop is reused and the cost drops accordingly.
sync_to_async()¶
- sync_to_async(sync_function, thread_sensitive=True)¶
Tar en sync-funktion och returnerar en async-funktion som omsluter den. Kan användas som antingen en direkt omslutning eller en dekorator:
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
@sync_to_async
def sync_function(): ...
Värdena för Threadlocals och contextvars bevaras över gränsen i båda riktningarna.
Synkroniseringsfunktioner brukar skrivas med antagandet att de alla körs i huvudtråden, så sync_to_async() har två trådningslägen:
thread_sensitive=True(standard): Synkroniseringsfunktionen körs i samma tråd som alla andrathread_sensitivefunktioner. Detta kommer att vara huvudtråden, om huvudtråden är synkron och du använderasync_to_sync()-omslaget.thread_sensitive=False: Synkroniseringsfunktionen körs i en helt ny tråd som sedan stängs när anropet har slutförts.
Trådkänsligt läge är ganska speciellt och gör en hel del arbete för att köra alla funktioner i samma tråd. Observera dock att det förlitar sig på användning av async_to_sync() över det i stacken för att korrekt köra saker på huvudtråden. Om du använder asyncio.run() eller liknande kommer den att falla tillbaka till att köra trådkänsliga funktioner i en enda, delad tråd, men detta kommer inte att vara huvudtråden.
Anledningen till att detta behövs i Django är att många bibliotek, särskilt databasadaptrar, kräver att de nås i samma tråd som de skapades i. Även en hel del befintlig Django-kod förutsätter att allt körs i samma tråd, t.ex. middleware som lägger till saker i en begäran för senare användning i vyer.
I stället för att införa potentiella kompatibilitetsproblem med den här koden valde vi istället att lägga till det här läget så att all befintlig Django sync-kod körs i samma tråd och därmed är helt kompatibel med async-läget. Observera att synkroniseringskoden alltid kommer att vara i en annan tråd än någon asynkron kod som anropar den, så du bör undvika att skicka råa databashandtag eller andra trådkänsliga referenser runt.
Within a single request, multiple thread_sensitive calls serialize on that
request’s worker thread, but each request gets its own per-context worker, so
concurrent requests do not serialize against each other. This mirrors
Django’s connection-per-thread model, and the same constraint applies in other
async database libraries, where concurrent queries on a single connection
serialize on a lock. To support more concurrent requests, increase the
connection pool size accordingly rather than disabling thread_sensitive.
I praktiken innebär denna begränsning att du inte bör skicka egenskaper hos databasens connection-objekt när du anropar sync_to_async(). Om du gör det kommer det att utlösa trådsäkerhetskontrollerna:
# DJANGO_SETTINGS_MODULE=settings.py python -m asyncio
>>> import asyncio
>>> from asgiref.sync import sync_to_async
>>> from django.db import connection
>>> # In an async context so you cannot use the database directly:
>>> connection.cursor()
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from
an async context - use a thread or sync_to_async.
>>> # Nor can you pass resolved connection attributes across threads:
>>> await sync_to_async(connection.cursor)()
django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread
can only be used in that same thread. The object with alias 'default' was
created in thread id 4371465600 and this is thread id 6131478528.
Istället bör du kapsla in all databasåtkomst i en hjälpfunktion som kan anropas med sync_to_async() utan att förlita sig på anslutningsobjektet i den anropande koden.