Asynkront stödLink to this heading

Django har stöd för att skriva asynkrona (”async”) vyer samt en helt asynkront aktiverad begärandestack om du kör under ASGI. Asynkrona vyer fungerar fortfarande under WSGI, men med en liten anpassningskostnad per begäran (se Prestanda) och utan möjligheten att ha effektiva långvariga begäranden.

Många delar av Django tillhandahåller asynkrona API:er, bland annat ORM:en, cache-ramverket, autentisering, sessioner och signaler. För annan kod är adaptern sync_to_async() en enkel brygga (se Prestanda). Ett stort antal asynkront inhemska Python-bibliotek kan också integreras.

Asynkrona vyerLink to this heading

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()).

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.

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:

Code
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örerLink to this heading

Följande dekoratorer kan användas med både synkrona och asynkrona vyfunktioner:

Till exempel:

Code
from django.views.decorators.cache import never_cache


@never_cache
def my_sync_view(request): ...


@never_cache
async def my_async_view(request): ...

method_decorator() kan användas med asynkrona metoder, inklusive async def-vyhanterare. Observera dock att om du dekorerar dispatch() på en View med asynkrona hanterare måste du också åsidosätta dispatch så att den blir async def:

Code
class MyClass(View):
    @method_decorator(never_cache)
    async def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    async def get(self, request): ...

    async def post(self, request): ...

Eftersom vi här dekorerar dispatch i stället för de enskilda hanteringsmetoderna måste vi göra dispatch asynkron så att method_decorator kan markera den resulterande metoden korrekt som en korutinfunktion.

Frågor och ORMLink to this heading

Med vissa undantag kan Django köra ORM-frågor asynkront:

Code
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 med a-prefix.

  • async for stöds på alla QuerySets (inklusive utdata från values() och values_list().)

Asynkrona modellmetoder som använder databasen stöds också:

Code
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().

Beständiga databasanslutningar, som anges med inställningen CONN_MAX_AGE, bör också inaktiveras i asynkront läge. Använd i stället databasbackendens inbyggda anslutningspoolning om den finns tillgänglig, eller undersök ett tredjepartsalternativ för anslutningspoolning om det krävs. Precis som i synkront Django delar samtidiga begäranden i en process den poolen, så dimensionera den för den avsedda samtidigheten av pågående frågor.

PrestandaLink to this heading

När du kör i ett läge som inte motsvarar vyn (t.ex. en asynkron vy under WSGI eller en traditionell synkron vy under ASGI) måste Django emulera den andra anropsstilen för att koden ska kunna köras. Kostnaden per anrop för denna anpassning är liten: tiotals mikrosekunder i ASGI-sökvägen inom begäran, där den körande händelseslingan återanvänds, och några hundra mikrosekunder i kallstartsökvägen som används av hanteringskommandon, bakgrundsuppgifter och skript. Jämfört med typiska begärandetider på millisekunder märks detta sällan i sig, men kan bli märkbart vid GIL-konkurrens när antalet aktiva trådar ökar.

Om du upptäcker att du omsluter enskilda rader eller åtgärder i en snäv loop bör du strukturera om koden så att loopen körs inom en enda övergång med sync_to_async() (eller async_to_sync()). Kostnaden per anrop för kontextväxlingen fördelas då över hela loopen och försvinner i praktiken.

Samma anpassningskostnad per anrop gäller för middleware. Django försöker minimera antalet kontextväxlingar mellan synkront och asynkront. Om du har en ASGI-server men all middleware och alla vyer är synkrona växlar den bara en gång, innan den går in i middlewarestacken.

Om du däremot lägger synkron middleware mellan en ASGI-server och en asynkron vy måste den växla till synkront läge för middleware och sedan tillbaka till asynkront läge för vyn. Django håller också den synkrona tråden öppen för spridning av middlewareundantag. För begärande-/svars-vyer som använder ORM och returnerar är detta vanligen ingen betydande kostnad. Det spelar störst roll när du använder ASGI för hög samtidig körning i processen över I/O som inte är ORM (t.ex. vidarebefordran till flera HTTP-källor, server-sända händelser eller andra långlivade begäranden), där den extra tråden per begäran begränsar samtidigheten.

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ånkopplingarLink to this heading

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:

Code
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äkerhetLink to this heading

DJANGO_ALLOW_ASYNC_UNSAFELink to this definition

Vissa centrala delar av Django kan inte användas säkert i en asynkron miljö eftersom de har globalt tillstånd som inte är coroutine-medvetet. Dessa delar av Django klassificeras som ”async-unsafe” och skyddas från körning i en asynkron miljö. ORM:ens synkrona API är huvudexemplet, men andra delar skyddas också på detta sätt.

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:

Shell
%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:

Shell
%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.

Om du behöver göra detta från Python, gör det med os.environ:

Code
import os

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

Asynkrona adapterfunktionerLink to this heading

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()Link to this heading

async_to_sync(async_function, force_new_loop=False)Link to this definition

Tar en async-funktion och returnerar en sync-funktion som omsluter den. Kan användas som antingen en direkt omslutning eller en dekorator:

Code
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() är i huvudsak en kraftfullare version av funktionen asyncio.run() i Pythons standardbibliotek. Förutom att säkerställa att trådlokala data fungerar aktiverar den också läget thread_sensitive i sync_to_async() när den omslutningen används under den. I den kalla sökvägen (ingen körande händelseslinga) betalar den kostnaden för att starta en ny händelseslinga, precis som asyncio.run(); när en händelseslinga redan körs (ASGI-fallet inom begäran) återanvänds den och kostnaden minskar därefter.

sync_to_async()Link to this heading

sync_to_async(sync_function, thread_sensitive=True)Link to this definition

Tar en sync-funktion och returnerar en async-funktion som omsluter den. Kan användas som antingen en direkt omslutning eller en dekorator:

Code
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 andra thread_sensitive funktioner. Detta kommer att vara huvudtråden, om huvudtråden är synkron och du använder async_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.

Inom en enskild begäran serialiseras flera thread_sensitive-anrop på begärans arbetstråd, men varje begäran får en egen arbetare per kontext, så samtidiga begäranden serialiseras inte mot varandra. Detta återspeglar Djangos modell med en anslutning per tråd, och samma begränsning gäller i andra asynkrona databasbibliotek där samtidiga frågor på en enda anslutning serialiseras med ett lås. Om du vill stödja fler samtidiga begäranden ökar du anslutningspoolens storlek i stället för att inaktivera 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:

Python console
# 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.