Django tiene soporte para escribir vistas asíncronas (“asíncronas”), junto con una pila de solicitudes completamente asíncrona habilitada si está ejecutando bajo ASGI. Las vistas asíncronas seguirán funcionando con WSGI, pero con penalizaciones de rendimiento y sin la capacidad de tener solicitudes eficientes de larga duración.

Todavía estamos trabajando en el soporte asíncrono para ORM y otras partes de Django. Puede esperar ver esto en versiones futuras. Por ahora, puede utilizar el sync_to_async() adaptador para interactuar con las partes de sincronización de Django. También hay una amplia gama de bibliotecas de Python nativas asíncronas con las que puede integrarse.

Cambiado en Django 3.1:

Se agregó soporte para vistas asíncronas.

Vistas asíncronas

Nuevo en Django 3.1.

Cualquier vista se puede declarar asincrónica haciendo que la parte invocable devuelva una corrutina; comúnmente, esto se hace usando async def. Para una vista basada en funciones, esto significa declarar la vista completa usando async def. Para una vista basada en clases, esto significa hacer su __call__() método un async def (no es __init__() o as_view()).

Nota

Usos de Django asyncio.iscoroutinefunction para probar si su vista es asincrónica o no. Si implementa su propio método para devolver una corrutina, asegúrese de configurar el _is_coroutine attribute de la vista a asyncio.coroutines._is_coroutine entonces esta función regresa True.

En un servidor WSGI, las vistas asíncronas se ejecutarán en su propio bucle de eventos único. Esto significa que puede usar funciones asíncronas, como solicitudes HTTP asíncronas concurrentes, sin ningún problema, pero no obtendrá los beneficios de una pila asíncrona.

Los principales beneficios son la capacidad de dar servicio a cientos de conexiones sin utilizar subprocesos de Python. Esto le permite usar transmisión lenta, sondeo largo y otros tipos de respuesta interesantes.

Si desea usar estos, deberá implementar Django usando ASGI en lugar de.

Advertencia

Solo obtendrá los beneficios de una pila de solicitudes totalmente asincrónica si tiene sin middleware sincrónico cargado en su sitio. Si hay una pieza de middleware sincrónico, entonces Django debe usar un hilo por solicitud para emular de forma segura un entorno sincrónico para él.

El middleware se puede construir para admitir tanto sincronizados como asincrónicos contextos. Parte del middleware de Django está construido así, pero no todos. Para ver qué middleware tiene que adaptarse Django, puede activar el registro de depuración para el django.request logger y busque mensajes de registro sobre “Middleware síncrono … adaptado”.

Tanto en el modo ASGI como en el WSGI, puede seguir utilizando de forma segura el soporte asincrónico para ejecutar código de forma simultánea en lugar de en serie. Esto es especialmente útil cuando se trata de API externas o almacenes de datos.

Si desea llamar a una parte de Django que todavía es síncrona, como el ORM, deberá envolverla en un sync_to_async() llama. Por ejemplo:

from asgiref.sync import sync_to_async

results =await sync_to_async(Blog.objects.get, thread_sensitive=True)(pk=123)

Puede que le resulte más fácil mover cualquier código ORM a su propia función y llamar a toda esa función usando sync_to_async(). Por ejemplo:

from asgiref.sync import sync_to_async

def_get_blog(pk):return Blog.objects.select_related('author').get(pk=pk)

get_blog = sync_to_async(_get_blog, thread_sensitive=True)

Si accidentalmente intenta llamar a una parte de Django que todavía es síncrona, solo desde una vista asíncrona, activará la función de Django protección de seguridad asíncrona para proteger sus datos de la corrupción.

Rendimiento

Cuando se ejecuta en un modo que no coincide con la vista (por ejemplo, una vista asíncrona en WSGI o una vista de sincronización tradicional en ASGI), Django debe emular el otro estilo de llamada para permitir que su código se ejecute. Este cambio de contexto provoca una pequeña penalización de rendimiento de alrededor de un milisegundo.

Esto también es true de middleware. Django intentará minimizar el número de cambios de contexto entre sincronización y asincrónica. Si tiene un servidor ASGI, pero todo su middleware y sus vistas son síncronos, cambiará solo una vez, antes de ingresar a la pila de middleware.

Sin embargo, si coloca middleware síncrono entre un servidor ASGI y una vista asincrónica, tendrá que cambiar al modo de sincronización para el middleware y luego volver al modo asíncrono para la vista. Django también mantendrá abierto el hilo de sincronización para la propagación de excepciones de middleware. Es posible que esto no se note al principio, pero agregar esta penalización de un hilo por solicitud puede eliminar cualquier ventaja de rendimiento asincrónico.

Debe realizar sus propias pruebas de rendimiento para ver qué efecto tiene ASGI frente a WSGI en su código. En algunos casos, puede haber un aumento de rendimiento incluso para una base de código puramente síncrona bajo ASGI porque el código de manejo de solicitudes todavía se ejecuta de forma asincrónica. En general, solo querrá habilitar el modo ASGI si tiene código asíncrono en su proyecto.

Seguridad asincrónica

DJANGO_ALLOW_ASYNC_UNSAFE

Cierto key partes de Django no pueden operar de forma segura en un entorno asincrónico, ya que tienen un estado global que no es compatible con las rutinas. Estas partes de Django se clasifican como “asíncronas inseguras” y están protegidas contra la ejecución en un entorno asíncrono. El ORM es el ejemplo principal, pero hay otras partes que también están protegidas de esta manera.

Si intenta ejecutar cualquiera de estas partes desde un hilo donde hay un bucle de eventos en ejecución, obtendrás un SynchronousOnlyOperation error. Tenga en cuenta que no es necesario que esté dentro de una función asíncrona directamente para que se produzca este error. Si ha llamado a una función de sincronización directamente desde una función asíncrona, sin usar sync_to_async() o similar, entonces también puede ocurrir. Esto se debe a que su código todavía se está ejecutando en un hilo con un bucle de eventos activo, aunque no se pueda declarar como código asíncrono.

Si encuentra este error, debe corregir su código para que no llame al código ofensivo desde un contexto asincrónico. En su lugar, escriba su código que hable con funciones asíncronas inseguras en su propia función de sincronización y llámelo usando asgiref.sync.sync_to_async() (o cualquier otra forma de ejecutar código de sincronización en su propio hilo).

Es posible que aún se vea obligado a ejecutar código de sincronización desde un contexto asincrónico. Por ejemplo, si el requisito se lo impone un entorno externo, como en un Jupyter computadora portátil. Si está seguro de que no hay posibilidad de que el código se ejecute al mismo tiempo, y absolutamente necesita ejecutar este código de sincronización desde un contexto asincrónico, luego puede deshabilitar la advertencia configurando el DJANGO_ALLOW_ASYNC_UNSAFE variable de entorno a cualquier valor.

Advertencia

Si habilita esta opción y hay acceso simultáneo a las partes asíncronas inseguras de Django, puede sufrir pérdida o corrupción de datos. Tenga mucho cuidado y no lo utilice en entornos de producción.

Si necesita hacer esto desde Python, hágalo con os.environ:

import os

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

Funciones del adaptador asíncrono

Es necesario adaptar el estilo de llamada cuando se llama al código de sincronización desde un contexto asincrónico o viceversa. Para ello existen dos funciones de adaptador, desde el asgiref.sync módulo: async_to_sync() y sync_to_async(). Se utilizan para hacer la transición entre los estilos de llamada y, al mismo tiempo, preservar la compatibilidad.

Estas funciones de adaptador se utilizan ampliamente en Django. los asgiref El paquete en sí es parte del proyecto Django, y se instala automáticamente como una dependencia cuando instala Django con pip.

async_to_sync()

async_to_sync(async_function, force_new_loop=False)

Toma una función asíncrona y devuelve una función de sincronización que la envuelve. Se puede utilizar como envoltorio directo o como decorador:

from asgiref.sync import async_to_sync

asyncdefget_data(...):...

sync_get_data = async_to_sync(get_data)@async_to_syncasyncdefget_other_data(...):...

La función asíncrona se ejecuta en el bucle de eventos para el hilo actual, si hay uno presente. Si no hay un ciclo de eventos actual, se activa un nuevo ciclo de eventos específicamente para la invocación asíncrona única y se apaga nuevamente una vez que se completa. En cualquier situación, la función asíncrona se ejecutará en un hilo diferente al código de llamada.

Los valores de Threadlocals y contextvars se conservan a través del límite en ambas direcciones.

async_to_sync() es esencialmente una versión más poderosa del asyncio.run() función en la biblioteca estándar de Python. Además de garantizar el funcionamiento de threadlocals, también permite thread_sensitive modo de sync_to_async() cuando esa envoltura se usa debajo de ella.

sync_to_async()

sync_to_async(sync_function, thread_sensitive=True)

Toma una función de sincronización y devuelve una función asíncrona que la envuelve. Se puede utilizar como envoltorio directo o como decorador:

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

Los valores de Threadlocals y contextvars se conservan a través del límite en ambas direcciones.

Las funciones de sincronización tienden a escribirse asumiendo que todas se ejecutan en el hilo principal, por lo que sync_to_async() tiene dos modos de subprocesamiento:

  • thread_sensitive=True (el valor predeterminado): la función de sincronización se ejecutará en el mismo hilo que todos los demás thread_sensitive funciones. Este será el hilo principal, si el hilo principal es síncrono y está utilizando el async_to_sync() envoltura.
  • thread_sensitive=False: la función de sincronización se ejecutará en un hilo nuevo que luego se cerrará una vez que se complete la invocación.

Advertencia

asgiref la versión 3.3.0 cambió el valor predeterminado de la thread_sensitive parámetro a True. Este es un valor predeterminado más seguro y, en muchos casos, al interactuar con Django el valor correcto, pero asegúrese de evaluar los usos de sync_to_async() si actualiza asgiref de una versión anterior.

El modo sensible a subprocesos es bastante especial y hace mucho trabajo para ejecutar todas las funciones en el mismo subproceso. Sin embargo, tenga en cuenta que se basa en el uso deasync_to_sync()encima de él en la pila para ejecutar correctamente las cosas en el hilo principal. Si utiliza asyncio.run() o similar, recurrirá a la ejecución de funciones sensibles a subprocesos en un solo subproceso compartido, pero este no será el subproceso principal.

La razón por la que esto es necesario en Django es que muchas bibliotecas, específicamente adaptadores de bases de datos, requieren que se acceda a ellas en el mismo hilo en el que fueron creadas. Además, una gran cantidad de código Django existente asume que todo se ejecuta en el mismo hilo, por ejemplo, agregar middleware. cosas a una solicitud para su uso posterior en vistas.

En lugar de introducir posibles problemas de compatibilidad con este código, optamos por agregar este modo para que todo el código de sincronización de Django existente se ejecute en el mismo hilo y, por lo tanto, sea totalmente compatible con el modo asíncrono. Tenga en cuenta que el código de sincronización siempre estará en un diferente subproceso a cualquier código asíncrono que lo esté llamando, por lo que debe evitar pasar identificadores de base de datos sin procesar u otras referencias sensibles a subprocesos.