# 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. - Published: 2026-07-03 - Updated: 2026-07-07 - Author: SharpSkill - Tags: django, async, asgi, python, performance - Reading time: 10 min --- 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. > **Quando l'async aiuta davvero** > > 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](https://docs.djangoproject.com/en/5.2/topics/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`. ```python # 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](https://asgi.readthedocs.io/en/latest/) 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](https://www.uvicorn.org/) 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. ```bash # 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:8000 ``` I 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](/technologies/django/interview-questions/django-deployment). ## 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. > **SynchronousOnlyOperation è una feature** > > 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. ```python # 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](https://github.com/django/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. ```python # 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. ```python # 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](/blog/django/django-celery-async-tasks-interview-2026). 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](/blog/django/django-orm-optimizing-queries). ## 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-worker` sotto 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`, `acreate` e `async for` dentro le async view, ricordando che l'ORM esegue comunque le query in un threadpool anziché in modo nativamente asincrono. - Trattare `SynchronousOnlyOperation` come una protezione: risolverla con metodi asincroni o `sync_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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/django/django-async-views-asgi-2026