Django async view e ASGI nel 2026: performance e domande da colloquio
Un'analisi approfondita delle async view di Django e di ASGI nel 2026: come funzionano internamente, quale server usare in produzione, l'ORM asincrono e la trappola SynchronousOnlyOperation, oltre alle domande da colloquio.

Le async view di Django permettono a un singolo worker di gestire centinaia di richieste I/O-bound concorrenti senza dedicare un thread a ogni connessione, ma solo quando il percorso del codice resta asincrono dalla view fino a ogni chiamata di rete. Da quando Django 3.1 ha introdotto le view async def e Django 4.1 ha aggiunto l'interfaccia ORM asincrona, il framework è diventato una piattaforma ASGI credibile, eppure la maggior parte dei deployment in produzione gira ancora sotto WSGI e lascia inutilizzata quella concorrenza. Questa analisi approfondita spiega come si comportano le async view sotto ASGI, quali server usare nel 2026, le trappole dell'ORM che sollevano SynchronousOnlyOperation e le domande da colloquio che distinguono chi ha portato in produzione Django asincrono da chi ne ha solo letto.
Le async view rendono quando una richiesta passa la maggior parte del tempo in attesa di I/O che non controlla: API HTTP di terze parti, servizi downstream lenti o risposte in streaming di lunga durata. Per il lavoro CPU-bound o le query veloci su database locali, una view sincrona supportata da più worker Gunicorn è di solito più rapida e molto più semplice da ragionare.
Come funzionano le async view di Django sotto ASGI
WSGI è fondamentalmente sincrono: ogni richiesta in corso occupa un thread o un processo worker dall'inizio alla fine, quindi 50 richieste lente richiedono 50 worker. ASGI sostituisce quel modello con un event loop che intreccia molte richieste su un singolo worker, sospendendo una coroutine ogni volta che questa attende dell'I/O e riprendendola quando i dati arrivano. La documentazione ufficiale su Django async descrive i due adattatori che rendono possibile questa coesistenza: sotto ASGI, Django esegue le view async def nativamente sul loop e sposta le view sincrone in un threadpool con sync_to_async; sotto WSGI, incapsula le async view in async_to_sync e le esegue fino al completamento, scartando ogni beneficio di concorrenza.
La conseguenza pratica è netta: una view async def deployata su un server WSGI si comporta come una view sincrona più lenta. La concorrenza si materializza solo su un server ASGI. Una async view minimale che chiama un'API esterna appare quasi identica alla sua controparte sincrona, con la differenza concentrata nell'await.
# views.py
import httpx
from django.http import JsonResponse
async def dashboard(request):
# Sotto ASGI questa coroutine gira sull'event loop, quindi il worker è
# libero di servire altre richieste mentre httpx attende il round-trip di rete.
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get("https://api.example.com/metrics")
return JsonResponse(response.json())Il vantaggio qui non è che la singola richiesta finisca prima; impiega lo stesso tempo reale. Il vantaggio è che il worker non è bloccato durante l'await, quindi il throughput sotto carico concorrente cresce mentre il numero di processi resta invariato.
Lo stesso modello sblocca pattern di risposta scomodi sotto WSGI. Da Django 4.2, StreamingHttpResponse accetta un generatore asincrono, quindi una view può restituire i chunk man mano che una sorgente upstream li produce, cosa adatta ai Server-Sent Events e agli stream di token LLM in proxy, dove la connessione resta aperta per secondi. I protocolli bidirezionali persistenti come i WebSocket passano ancora attraverso Django Channels anziché dalle semplici view HTTP, ma entrambi poggiano sulla stessa base ASGI, quindi un progetto che adotta le async view ha già pagato il costo infrastrutturale per le funzionalità real-time future.
Scegliere un server ASGI per Django nel 2026
Django fornisce un oggetto applicazione ASGI in asgi.py, ma non si esegue da solo; un server ASGI dedicato guida l'event loop. La specifica ASGI definisce l'interfaccia che ognuno di questi server implementa, motivo per cui sono intercambiabili a livello di protocollo e si differenziano soprattutto per performance e copertura dei protocolli.
| Server | Implementato in | Caso d'uso ideale | |--------|----------------|----------| | Uvicorn | Python (uvloop) | ASGI generico, HTTP/1.1, la scelta predefinita più comune | | Daphne | Python | Django Channels, WebSocket, HTTP/2 | | Granian | Rust | Massimo throughput grezzo, HTTP/1 e HTTP/2 | | Hypercorn | Python | HTTP/2 e HTTP/3 (QUIC) |
Per la maggior parte dei deployment del 2026, Uvicorn eseguito come worker class di Gunicorn resta la scelta affidabile: Gunicorn supervisiona il ciclo di vita dei processi, riavvia i worker in crash e gestisce i segnali, mentre Uvicorn guida l'event loop dentro ciascuno di essi. Un dettaglio di migrazione manda in confusione gli aggiornamenti: le worker class di Gunicorn sono uscite dal core di Uvicorn per confluire in un pacchetto separato uvicorn-worker a partire da Uvicorn 0.30, quindi il percorso di import è cambiato.
# Installare prima il pacchetto worker: pip install uvicorn-worker
gunicorn myproject.asgi:application \
--workers 4 \
--worker-class uvicorn_worker.UvicornWorker \
--bind 0.0.0.0:8000I team che inseguono il massimo throughput scelgono sempre più spesso Granian, un server basato su Rust che elimina l'overhead del parsing HTTP lato Python. Qualunque sia la scelta, il tema del deployment è il motivo per cui l'async compare in produzione solo quando viene configurato deliberatamente; la configurazione specifica per ASGI rispecchia l'attenzione operativa trattata nel modulo domande da colloquio sul deployment di Django.
L'ORM asincrono e la trappola SynchronousOnlyOperation
Django 4.1 ha aggiunto metodi di query asincroni in tutto l'ORM: aget, acreate, asave, adelete, acount, aexists, aaggregate e l'iterazione con async for. Questi offrono una superficie async-friendly, ma un caveat critico sopravvive fino al 2026: lo strato di database sottostante non è nativamente asincrono. Anche con psycopg3, Django esegue le query in un threadpool e le espone tramite wrapper asincroni, quindi l'ORM asincrono migliora l'ergonomia ed evita di bloccare il loop senza però fornire un vero I/O di database asincrono end-to-end.
La modalità di errore più comune è chiamare un metodo ORM sincrono direttamente dentro una async view. Django rileva il contesto asincrono e si rifiuta, sollevando SynchronousOnlyOperation per impedire che una chiamata bloccante congeli l'event loop per tutte le altre richieste che condividono quel worker.
L'eccezione è una protezione, non un bug. Ricorrere a DJANGO_ALLOW_ASYNC_UNSAFE=true per silenziarla reintroduce esattamente il comportamento bloccante che l'async doveva eliminare. La correzione giusta è un metodo ORM asincrono (aget al posto di get) o un wrapper sync_to_async esplicito attorno al codice che non ha equivalente asincrono.
Le async view risultano pulite una volta che i metodi di query asincroni sostituiscono i loro gemelli sincroni. Il pattern seguente conta le righe e scorre i primi dieci record senza mai lasciare l'event loop.
# views.py
from django.http import JsonResponse
from .models import Article
async def latest_articles(request):
# acount() e l'iterazione asincrona sono wrapper async-safe intorno all'ORM.
count = await Article.objects.acount()
titles = []
async for article in Article.objects.order_by("-created_at")[:10]:
titles.append(article.title)
return JsonResponse({"count": count, "titles": titles})Alcuni codici non hanno controparte asincrona: un SDK di terze parti che offre solo chiamate sincrone, o una catena fitta di traversal dell'ORM non conveniente da riscrivere. Incapsularlo in sync_to_async della libreria asgiref sposta il lavoro su un thread e mantiene il loop reattivo. L'argomento thread_sensitive=True è il valore predefinito sicuro perché instrada ogni chiamata incapsulata verso un unico thread executor condiviso, preservando la connessione al database e lo stato della transazione, che si romperebbero se le chiamate si disperdessero su thread arbitrari.
# views.py
from asgiref.sync import sync_to_async
from .services import build_report # una funzione sincrona
async def report_view(request):
# thread_sensitive=True mantiene le chiamate incapsulate su un unico thread condiviso,
# preservando lo stato di connessione e transazione attraverso il confine.
report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id)
return JsonResponse(report)Performance di Django async: quando la concorrenza vince
La performance di Django async è una questione di sovrapposizione dell'I/O, non di velocità grezza. Il vantaggio più evidente arriva dal distribuire chiamate di rete indipendenti con asyncio.gather: tre richieste esterne da 200ms ciascuna costano circa 600ms in sequenza ma si completano in circa 200ms in modo concorrente, perché gli await si sovrappongono sullo stesso loop.
# views.py
import asyncio
import httpx
from django.http import JsonResponse
async def aggregate(request):
async with httpx.AsyncClient(timeout=5.0) as client:
# Tre chiamate indipendenti girano in modo concorrente invece che una dopo l'altra.
weather, prices, news = await asyncio.gather(
client.get("https://api.example.com/weather"),
client.get("https://api.example.com/prices"),
client.get("https://api.example.com/news"),
)
return JsonResponse({
"weather": weather.json(),
"prices": prices.json(),
"news": news.json(),
})Il caso inverso conta altrettanto. Una view che esegue una singola query locale veloce non guadagna nulla dall'async e spesso ci perde, perché ogni richiesta paga ora lo scheduling dell'event loop più un salto nel threadpool verso il driver sincrono del database. L'async impone anche una tassa più sottile a ogni confine sync/async: mescolare middleware sincroni e asincroni obbliga Django a passare tra il loop e un thread a ogni transizione, e una richiesta che attraversa quella linea più volte per risposta accumula latenza reale. Mantenere lo stack di middleware uniformemente async, o uniformemente sync, elimina quei passaggi.
La decisione si riduce alla forma del carico di lavoro più che a una preferenza per la sintassi moderna.
| Carico di lavoro | Scelta predefinita migliore |
|----------|----------------|
| Più chiamate ad API esterne lente per richiesta | Async view con asyncio.gather |
| Streaming di lunga durata o Server-Sent Events | Async view con un generatore asincrono |
| Singola query veloce su database, poco CPU | View sincrona, più worker Gunicorn |
| Rendering o calcolo CPU-bound | View sincrona, delega a una task queue |
Per i job realmente di lunga durata, le async view sono lo strumento del tutto sbagliato; tenere aperta una richiesta per svolgere minuti di lavoro spreca la connessione indipendentemente dal modello di concorrenza. Quel lavoro appartiene a un worker in background, un compromesso esplorato nella guida sui task asincroni con Celery in Django.
L'adozione dovrebbe seguire la misurazione piuttosto che l'intuizione. Il modo onesto di giustificare l'async è sottoporre l'endpoint specifico a un load test con uno strumento come Locust o k6 in condizioni di concorrenza realistiche, confrontando una versione sincrona su N worker con una versione asincrona sullo stesso hardware. Gli endpoint dominati da una singola chiamata esterna ad alta latenza mostrano un divario ampio a favore dell'async; gli endpoint dominati da query veloci mostrano spesso l'async leggermente indietro una volta contato il salto nel threadpool. Profilare prima la richiesta rivela anche se il collo di bottiglia sia effettivamente l'I/O, perché l'async non fa nulla per una view lenta a causa di query senza indice, un problema affrontato nella guida su come ottimizzare le query dell'ORM di Django.
Pronto a superare i tuoi colloqui su Django?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Domande da colloquio su Django async per il 2026
Un colloquio su Django async verifica se il candidato comprende il modello o si limita a riconoscere le parole chiave. Le domande seguenti riflettono ciò che i colloqui senior chiedono davvero nel 2026, ciascuna abbinata alla risposta che darebbe un ingegnere esperto.
Cosa cambia quando una async view gira sotto WSGI invece che sotto ASGI? Sotto WSGI, Django incapsula la coroutine in async_to_sync e la esegue fino al completamento sul thread della richiesta, quindi si comporta come una view sincrona più lenta senza alcun beneficio di concorrenza. Solo un server ASGI che esegue un event loop permette al worker di servire altre richieste durante un await.
Perché Article.objects.get(pk=1) solleva SynchronousOnlyOperation dentro una async view? La chiamata ORM sincrona bloccherebbe l'event loop, mandando in stallo tutte le altre richieste su quel worker. Django rileva il contesto asincrono e si rifiuta. La soluzione è il metodo asincrono await Article.objects.aget(pk=1), oppure sync_to_async per il codice senza equivalente asincrono.
L'ORM asincrono di Django è davvero non bloccante? No. I metodi asincroni sono wrapper ergonomici; le query sottostanti girano comunque in un threadpool perché i driver del database non sono integrati per l'esecuzione asincrona nativa end-to-end. Il beneficio è mantenere libero il loop, non un I/O di database parallelo a livello di driver.
Quando scegliere le async view invece di una task queue come Celery? Le async view sono adatte alla concorrenza di I/O legata alla richiesta che deve concludersi prima della risposta, come aggregare più API. Il lavoro che sopravvive alla richiesta, richiede retry o dura minuti appartiene a Celery o ai background task di Django, perché tenere aperta una connessione HTTP per esso spreca risorse del server.
Qual è il costo di mescolare middleware sincroni e asincroni? Ogni transizione tra un componente sincrono e uno asincrono obbliga Django a saltare tra l'event loop e un thread tramite sync_to_async o async_to_sync. Una richiesta che attraversa ripetutamente quel confine paga un overhead cumulativo, quindi uno stack di middleware uniforme rende meglio di uno alternato.
Conclusione
- Le async view forniscono concorrenza solo su un server ASGI; la stessa view sotto WSGI gira in modo sincrono tramite
async_to_sync. - Deployare Uvicorn tramite il pacchetto
uvicorn-workersotto Gunicorn, o Granian per il massimo throughput, e trattare la configurazione ASGI come un passo di deployment esplicito. - Ricorrere alle async view quando una richiesta attende più chiamate I/O esterne lente; mantenere sincroni, con più worker, gli endpoint con query veloci e quelli CPU-bound.
- Usare
aget,acreateeasync fordentro le async view, ricordando che l'ORM esegue comunque le query in un threadpool anziché in modo nativamente asincrono. - Trattare
SynchronousOnlyOperationcome una protezione: risolverla con metodi asincroni osync_to_async(thread_sensitive=True), mai disabilitando il controllo. - Mantenere lo stack di middleware uniformemente sincrono o asincrono per evitare di pagare una penalità di cambio thread a ogni attraversamento del confine.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

Django e Celery: Elaborazione Asincrona dei Task e Domande da Colloquio 2026
Guida completa a Django e Celery: configurazione, task asincroni, code, Celery Beat, monitoraggio e domande frequenti nei colloqui tecnici 2026.

Django e PostgreSQL nel 2026: indicizzazione, ricerca full-text e domande da colloquio
Guida pratica all'ottimizzazione di Django con PostgreSQL: indici B-tree, parziali e covering, ricerca full-text con SearchVector e GIN, più le domande da colloquio del 2026.

Django 6.0 nel 2026: Chiavi Primarie Composite, Background Tasks e Domande da Colloquio
Analisi tecnica approfondita di Django 6.0: chiavi primarie composite con CompositePrimaryKey, framework nativo per task in background, template partials, middleware CSP integrato e domande da colloquio per sviluppatori Python nel 2026.