# Vistas asíncronas de Django y ASGI en 2026: rendimiento y preguntas de entrevista > Un análisis a fondo de las vistas asíncronas de Django y ASGI en 2026: cómo funcionan por dentro, qué servidor desplegar, el ORM asíncrono y la trampa de SynchronousOnlyOperation, además de preguntas de entrevista. - Published: 2026-07-03 - Updated: 2026-07-07 - Author: SharpSkill - Tags: django, async, asgi, python, performance - Reading time: 10 min --- Las vistas asíncronas de Django permiten que un solo worker atienda cientos de solicitudes concurrentes con uso intensivo de I/O sin dedicar un thread a cada conexión, pero solo cuando la ruta de ejecución se mantiene asíncrona desde la vista hasta cada llamada de red. Desde que Django 3.1 introdujo las vistas `async def` y Django 4.1 sumó la interfaz asíncrona del ORM, el framework se convirtió en una plataforma ASGI creíble; aun así, la mayoría de los despliegues en producción sigue ejecutándose bajo WSGI y deja esa concurrencia sin aprovechar. Este análisis a fondo explica cómo se comportan las vistas asíncronas bajo ASGI, qué servidores conviene ejecutar en 2026, las trampas del ORM que lanzan `SynchronousOnlyOperation` y las preguntas de entrevista que distinguen a quienes ya desplegaron Django asíncrono de quienes solo leyeron sobre el tema. > **Cuándo el async realmente ayuda** > > Las vistas asíncronas rinden cuando una solicitud pasa la mayor parte del tiempo esperando I/O que no controla: APIs HTTP de terceros, servicios downstream lentos o respuestas de streaming de larga duración. Para trabajo con uso intensivo de CPU o consultas rápidas a una base de datos local, una vista síncrona respaldada por más workers de Gunicorn suele ser más rápida y mucho más fácil de razonar. ## Cómo funcionan las vistas asíncronas de Django bajo ASGI WSGI es fundamentalmente síncrono: cada solicitud en curso ocupa un thread o proceso worker de principio a fin, así que 50 solicitudes lentas necesitan 50 workers. ASGI reemplaza ese modelo por un event loop que intercala muchas solicitudes en un mismo worker, suspendiendo una coroutine cada vez que hace `await` sobre I/O y reanudándola cuando llegan los datos. La [documentación oficial de async de Django](https://docs.djangoproject.com/en/5.2/topics/async/) describe los dos adaptadores que hacen posible esta coexistencia: bajo ASGI, Django ejecuta las vistas `async def` de forma nativa en el loop y empuja las vistas síncronas a un threadpool con `sync_to_async`; bajo WSGI, envuelve las vistas asíncronas en `async_to_sync` y las ejecuta hasta el final, descartando todo beneficio de concurrencia. La consecuencia práctica es contundente: una vista `async def` desplegada en un servidor WSGI se comporta como una vista síncrona más lenta. La concurrencia solo se materializa en un servidor ASGI. Una vista asíncrona mínima que llama a una API externa se ve casi idéntica a su equivalente síncrona, con la diferencia concentrada en el `await`. ```python # views.py import httpx from django.http import JsonResponse async def dashboard(request): # Bajo ASGI esta coroutine corre en el event loop, así que el worker queda # libre para atender otras solicitudes mientras httpx espera el ida y vuelta de red. async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get("https://api.example.com/metrics") return JsonResponse(response.json()) ``` La ganancia aquí no es que la solicitud individual termine más rápido; toma el mismo tiempo de reloj. La ganancia es que el worker no queda bloqueado durante el `await`, de modo que el throughput bajo carga concurrente sube mientras la cantidad de procesos se mantiene constante. El mismo modelo habilita patrones de respuesta que resultan incómodos bajo WSGI. Desde Django 4.2, `StreamingHttpResponse` acepta un generador asíncrono, así que una vista puede emitir chunks a medida que una fuente upstream los produce, lo que encaja con Server-Sent Events y con streams de tokens de LLM proxeados, donde la conexión permanece abierta durante segundos. Los protocolos bidireccionales persistentes como los WebSockets siguen pasando por Django Channels en lugar de vistas HTTP planas, pero ambos se apoyan en la misma base ASGI, de modo que un proyecto que adopta vistas asíncronas ya pagó el costo de infraestructura para las funciones en tiempo real que vengan después. ## Cómo elegir un servidor ASGI para Django en 2026 Django incluye un objeto de aplicación ASGI en `asgi.py`, pero no se ejecuta solo; un servidor ASGI dedicado impulsa el event loop. La [especificación ASGI](https://asgi.readthedocs.io/en/latest/) define la interfaz que implementa cada uno de estos servidores, razón por la cual son intercambiables a nivel de protocolo y difieren sobre todo en rendimiento y cobertura de protocolos. | Servidor | Implementado en | Mejor para | |--------|----------------|----------| | Uvicorn | Python (uvloop) | ASGI general, HTTP/1.1, la opción por defecto habitual | | Daphne | Python | Django Channels, WebSockets, HTTP/2 | | Granian | Rust | Máximo throughput bruto, HTTP/1 y HTTP/2 | | Hypercorn | Python | HTTP/2 y HTTP/3 (QUIC) | Para la mayoría de los despliegues de 2026, [Uvicorn](https://www.uvicorn.org/) ejecutado como clase de worker de Gunicorn sigue siendo la opción confiable: Gunicorn supervisa el ciclo de vida del proceso, reinicia los workers que fallan y maneja las señales, mientras Uvicorn impulsa el event loop dentro de cada uno. Un detalle de migración complica las actualizaciones: las clases de worker de Gunicorn salieron del núcleo de Uvicorn hacia un paquete `uvicorn-worker` separado a partir de Uvicorn 0.30, así que la ruta de import cambió. ```bash # Instalar primero el paquete del worker: pip install uvicorn-worker gunicorn myproject.asgi:application \ --workers 4 \ --worker-class uvicorn_worker.UvicornWorker \ --bind 0.0.0.0:8000 ``` Los equipos que persiguen el máximo throughput recurren cada vez más a Granian, un servidor basado en Rust que elimina el overhead del parseo HTTP del lado de Python. Cualquiera sea la elección, la historia del despliegue es la razón por la que el async solo aparece en producción cuando se configura de forma deliberada; la puesta a punto específica de ASGI refleja el mismo cuidado operativo que cubre el módulo de [preguntas de entrevista sobre despliegue de Django](/technologies/django/interview-questions/django-deployment). ## El ORM asíncrono y la trampa de SynchronousOnlyOperation Django 4.1 sumó métodos de consulta asíncronos en todo el ORM: `aget`, `acreate`, `asave`, `adelete`, `acount`, `aexists`, `aaggregate` e iteración con `async for`. Estos ofrecen una superficie amigable con async, pero una advertencia crítica sobrevive hasta 2026: la capa de base de datos que está debajo no es nativamente asíncrona. Incluso con psycopg3, Django ejecuta las consultas en un threadpool y las expone a través de wrappers asíncronos, así que el ORM asíncrono mejora la ergonomía y evita bloquear el loop sin ofrecer un verdadero I/O de base de datos asíncrono de extremo a extremo. El modo de falla más común es llamar a un método síncrono del ORM directamente dentro de una vista asíncrona. Django detecta el contexto asíncrono y se niega, lanzando `SynchronousOnlyOperation` para evitar que una llamada bloqueante congele el event loop para todas las demás solicitudes que comparten ese worker. > **SynchronousOnlyOperation es una función, no un bug** > > La excepción es una barrera de protección, no un error. Recurrir a `DJANGO_ALLOW_ASYNC_UNSAFE=true` para silenciarla reintroduce exactamente el comportamiento bloqueante que el async buscaba eliminar. La solución correcta es un método asíncrono del ORM (`aget` en lugar de `get`) o un wrapper explícito con `sync_to_async` alrededor del código que no tiene equivalente asíncrono. Las vistas asíncronas se leen con claridad una vez que los métodos de consulta asíncronos reemplazan a sus gemelos síncronos. El patrón siguiente cuenta filas y transmite los primeros diez registros sin abandonar nunca el event loop. ```python # views.py from django.http import JsonResponse from .models import Article async def latest_articles(request): # acount() y la iteración asíncrona son wrappers async-safe sobre el 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}) ``` Parte del código no tiene contraparte asíncrona: un SDK de terceros que solo ofrece llamadas síncronas, o una cadena densa de traversals del ORM que no vale la pena reescribir. Envolverlo con `sync_to_async` de la [librería asgiref](https://github.com/django/asgiref) descarga el trabajo a un thread y mantiene el loop responsivo. El argumento `thread_sensitive=True` es el valor por defecto seguro porque enruta cada llamada envuelta a un único thread ejecutor compartido, preservando el estado de conexión y de transacción de la base de datos que se rompería si las llamadas se dispersaran por threads arbitrarios. ```python # views.py from asgiref.sync import sync_to_async from .services import build_report # una función síncrona async def report_view(request): # thread_sensitive=True mantiene las llamadas envueltas en un único thread compartido, # preservando el estado de conexión y de transacción a través del límite. report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id) return JsonResponse(report) ``` ## Rendimiento asíncrono de Django: cuándo gana la concurrencia El rendimiento asíncrono de Django es una historia sobre solapamiento de I/O, no sobre velocidad bruta. La ganancia más clara viene de desplegar en abanico llamadas de red independientes con `asyncio.gather`: tres solicitudes externas de 200 ms cada una cuestan alrededor de 600 ms en secuencia, pero se completan en cerca de 200 ms de forma concurrente, porque los awaits se solapan en el mismo 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: # Tres llamadas independientes corren de forma concurrente en lugar de una tras otra. 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(), }) ``` El caso inverso importa igual. Una vista que ejecuta una única consulta local rápida no gana nada con async y a menudo pierde, porque cada solicitud ahora paga la planificación del event loop más un salto al threadpool hacia el driver síncrono de la base de datos. El async también impone un impuesto más sutil en cada límite sync/async: mezclar middleware síncrono y asíncrono obliga a Django a alternar entre el loop y un thread en cada transición, y una solicitud que cruza esa línea varias veces por respuesta acumula latencia real. Mantener el stack de middleware uniformemente asíncrono, o uniformemente síncrono, elimina esos cambios. La decisión se reduce a la forma de la carga de trabajo más que a una preferencia por la sintaxis moderna. | Carga de trabajo | Mejor opción por defecto | |----------|----------------| | Múltiples llamadas lentas a APIs externas por solicitud | Vista asíncrona con `asyncio.gather` | | Streaming de larga duración o Server-Sent Events | Vista asíncrona con un generador asíncrono | | Una única consulta rápida a la base de datos, con poca CPU | Vista síncrona, más workers de Gunicorn | | Renderizado o cómputo con uso intensivo de CPU | Vista síncrona, descargar a una cola de tareas | Para trabajos genuinamente largos, las vistas asíncronas son la herramienta equivocada por completo; mantener una solicitud abierta para hacer minutos de trabajo desperdicia la conexión sin importar el modelo de concurrencia. Ese trabajo pertenece a un worker en segundo plano, un trade-off explorado en la guía sobre [tareas asíncronas con Celery en Django](/blog/django/django-celery-async-tasks-interview-2026). La adopción debería seguir a la medición y no a la intuición. La manera honesta de justificar el async es hacer pruebas de carga sobre el endpoint específico con una herramienta como Locust o k6 bajo concurrencia realista, comparando una versión síncrona sobre N workers contra una versión asíncrona sobre el mismo hardware. Los endpoints dominados por una única llamada externa con alta latencia muestran una brecha amplia a favor del async; los endpoints dominados por consultas rápidas con frecuencia muestran al async ligeramente por detrás una vez que se cuenta el salto al threadpool. Perfilar la solicitud primero también revela si el cuello de botella siquiera es I/O, ya que el async no hace nada por una vista que es lenta a causa de consultas sin índice, un problema abordado en el recorrido sobre [optimización de consultas del ORM de Django](/blog/django/django-orm-optimizing-queries). ## Preguntas de entrevista sobre Django asíncrono para 2026 Una entrevista sobre Django asíncrono examina si el candidato entiende el modelo o simplemente reconoce las palabras clave. Las preguntas a continuación reflejan lo que las entrevistas senior realmente preguntan en 2026, cada una acompañada de la respuesta que daría un ingeniero con experiencia. **¿Qué cambia cuando una vista asíncrona se ejecuta bajo WSGI en lugar de ASGI?** Bajo WSGI, Django envuelve la coroutine en `async_to_sync` y la ejecuta hasta el final en el thread de la solicitud, así que se comporta como una vista síncrona más lenta, sin ningún beneficio de concurrencia. Solo un servidor ASGI que ejecuta un event loop permite que el worker atienda otras solicitudes durante un `await`. **¿Por qué `Article.objects.get(pk=1)` lanza `SynchronousOnlyOperation` dentro de una vista asíncrona?** La llamada síncrona del ORM bloquearía el event loop, estancando todas las demás solicitudes de ese worker. Django detecta el contexto asíncrono y se niega. La solución es el método asíncrono `await Article.objects.aget(pk=1)`, o `sync_to_async` para el código sin equivalente asíncrono. **¿El ORM asíncrono de Django es realmente no bloqueante?** No. Los métodos asíncronos son wrappers ergonómicos; las consultas subyacentes siguen ejecutándose en un threadpool porque los drivers de base de datos no están integrados para una ejecución asíncrona nativa de extremo a extremo. El beneficio es mantener el loop libre, no un I/O de base de datos paralelo a nivel del driver. **¿Cuándo conviene elegir vistas asíncronas en lugar de una cola de tareas como Celery?** Las vistas asíncronas convienen para la concurrencia de I/O acotada a la solicitud que debe terminar antes de la respuesta, como agregar varias APIs. El trabajo que sobrevive a la solicitud, necesita reintentos o corre durante minutos pertenece a Celery o a las tareas en segundo plano de Django, ya que mantener una conexión HTTP abierta para eso desperdicia recursos del servidor. **¿Cuál es el costo de mezclar middleware síncrono y asíncrono?** Cada transición entre un componente síncrono y uno asíncrono obliga a Django a saltar entre el event loop y un thread mediante `sync_to_async` o `async_to_sync`. Una solicitud que cruza ese límite repetidamente paga un overhead acumulativo, así que un stack de middleware uniforme rinde mejor que uno intercalado. ## Conclusión - Las vistas asíncronas solo aportan concurrencia en un servidor ASGI; la misma vista bajo WSGI se ejecuta de forma síncrona a través de `async_to_sync`. - Desplegar Uvicorn mediante el paquete `uvicorn-worker` bajo Gunicorn, o Granian para el máximo throughput, y tratar la configuración de ASGI como un paso de despliegue explícito. - Recurrir a las vistas asíncronas cuando una solicitud espera múltiples llamadas de I/O externas lentas; mantener síncronos los endpoints de consultas rápidas y con uso intensivo de CPU con más workers. - Usar `aget`, `acreate` y `async for` dentro de las vistas asíncronas, y recordar que el ORM sigue ejecutando las consultas en un threadpool en lugar de forma nativamente asíncrona. - Tratar `SynchronousOnlyOperation` como una barrera de protección: corregirlo con métodos asíncronos o `sync_to_async(thread_sensitive=True)`, nunca deshabilitando la verificación. - Mantener el stack de middleware uniformemente síncrono o asíncrono para no pagar una penalización por cambio de thread en cada cruce de límite. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/django/django-async-views-asgi-2026