Поддержка асинхронного режима

New in Django 3.0.

Django поддерживает написание асинхронных («асинхронных») представлений, а также полностью асинхронный стек запросов, если вы работаете под управлением ASGI. Асинхронные представления будут работать и под WSGI, но с потерями в производительности и без возможности иметь эффективные длительные запросы.

Мы все еще работаем над поддержкой async для ORM и других частей Django. Вы можете ожидать увидеть это в будущих релизах. Пока же вы можете использовать адаптер sync_to_async() для взаимодействия с синхронизируемыми частями Django. Существует также целый ряд async-нативных библиотек Python, с которыми вы можете интегрироваться.

Changed in Django 3.1:

Добавлена поддержка асинхронных представлений.

Асинхронные представления

New in Django 3.1.

Любое представление можно объявить асинхронным, заставив вызываемую его часть возвращать корутину - обычно это делается с помощью async def. Для представления, основанного на функции, это означает объявление всего представления с помощью async def. Для представления, основанного на классе, это означает сделать его __call__() метод async def (а не __init__() или as_view()).

Примечание

Django использует asyncio.iscoroutinefunction для проверки того, является ли ваше представление асинхронным или нет. Если вы реализуете свой собственный метод возврата корутины, убедитесь, что вы установили атрибут _is_coroutine представления в asyncio.coroutines._is_coroutine, чтобы эта функция возвращала True.

В сервере WSGI асинхронные представления будут выполняться в собственном однократном цикле событий. Это означает, что вы можете использовать функции async, такие как одновременные async-запросы HTTP, без каких-либо проблем, но вы не получите преимуществ стека async.

Основные преимущества - возможность обслуживать сотни соединений без использования потоков Python. Это позволяет использовать медленные потоки, длинные опросы и другие интересные типы ответов.

Если вы хотите использовать их, вам нужно развернуть Django, используя ASGI вместо этого.

Предупреждение

Вы получите преимущества полностью асинхронного стека запросов только в том случае, если на вашем сайте нет никакого синхронного промежуточного ПО. Если есть синхронное промежуточное ПО, то Django должен использовать поток на запрос, чтобы безопасно эмулировать синхронную среду для него.

Среднее ПО может быть построено для поддержки контекстов both sync and async. Некоторые из промежуточных программ Django построены таким образом, но не все. Чтобы узнать, какое промежуточное ПО Django адаптировано, вы можете включить отладочную регистрацию для логгера django.request и поискать в логе сообщения о «Synchronous middleware … adapted «.

В режиме ASGI и WSGI можно безопасно использовать асинхронную поддержку для одновременного, а не последовательного выполнения кода. Это особенно удобно при работе с внешними API или хранилищами данных.

Если вы хотите вызвать часть Django, которая все еще синхронна, например, ORM, вам придется обернуть ее в вызов sync_to_async(). Например:

from asgiref.sync import sync_to_async

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

Возможно, вам будет проще перенести любой код ORM в собственную функцию и вызывать всю функцию с помощью sync_to_async(). Например:

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)

Если вы случайно попытаетесь вызвать часть Django, которая все еще синхронна, из асинхронного представления, вы запустите asynchronous safety protection Django, чтобы защитить ваши данные от повреждения.

Производительность

При работе в режиме, который не соответствует представлению (например, асинхронное представление в WSGI или традиционное синхронное представление в ASGI), Django должен эмулировать другой стиль вызова, чтобы ваш код мог работать. Это переключение контекста приводит к небольшому снижению производительности примерно на миллисекунду.

Это также относится и к промежуточному программному обеспечению. Django попытается минимизировать количество переключений контекста между sync и async. Если у вас есть ASGI-сервер, но все ваши промежуточные программы и представления синхронны, то переключение произойдет только один раз, до того, как он войдет в стек промежуточных программ.

Однако, если вы поместите синхронное промежуточное ПО между ASGI-сервером и асинхронным представлением, ему придется переключиться в режим синхронизации для промежуточного ПО, а затем обратно в режим асинхронизации для представления. Django также будет держать поток синхронизации открытым для распространения исключений промежуточного ПО. Поначалу это может быть незаметно, но добавление штрафа в виде одного потока на запрос может свести на нет все преимущества производительности асинхронного режима.

Вы должны провести собственное тестирование производительности, чтобы увидеть, какое влияние ASGI по сравнению с WSGI оказывает на ваш код. В некоторых случаях даже для чисто синхронной кодовой базы в режиме ASGI может наблюдаться увеличение производительности, поскольку код обработки запросов все еще выполняется асинхронно. В общем, вы захотите включить режим ASGI, только если в вашем проекте есть асинхронный код.

Асинхронная безопасность

DJANGO_ALLOW_ASYNC_UNSAFE

Некоторые ключевые части Django не могут безопасно работать в асинхронном окружении, так как имеют глобальное состояние, не ориентированное на корутину. Эти части Django классифицируются как «async-unsafe» и защищены от выполнения в среде async. ORM является основным примером, но есть и другие части, которые также защищены таким образом.

Если вы попытаетесь запустить любую из этих частей из потока, где есть запущенный цикл событий, вы получите ошибку SynchronousOnlyOperation. Обратите внимание, что для возникновения этой ошибки не обязательно находиться непосредственно внутри асинхронной функции. Если вы вызвали функцию синхронизации непосредственно из асинхронной функции, не используя sync_to_async() или аналогичное, то это также может произойти. Это происходит потому, что ваш код все еще выполняется в потоке с активным циклом событий, даже если он не объявлен как async-код.

Если вы столкнулись с этой ошибкой, вам следует исправить свой код, чтобы не вызывать небезопасный код из контекста async. Вместо этого напишите код, который обращается к async-небезопасным функциям, в собственной функции синхронизации и вызовите ее, используя asgiref.sync.sync_to_async() (или любой другой способ запуска кода синхронизации в собственном потоке).

Вы все еще можете быть вынуждены выполнять синхронизированный код из асинхронного контекста. Например, если это требование навязано вам внешней средой, например, в Jupyter notebook. Если вы уверены, что код не будет выполняться одновременно, и вам абсолютно необходимо запустить этот синхрокод из контекста async, то вы можете отключить предупреждение, установив переменную окружения DJANGO_ALLOW_ASYNC_UNSAFE в любое значение.

Предупреждение

Если вы включите эту опцию и будете иметь одновременный доступ к async-небезопасным частям Django, вы можете пострадать от потери или повреждения данных. Будьте очень осторожны и не используйте эту опцию в производственных средах.

Если вам нужно сделать это из Python, сделайте это с помощью os.environ:

import os

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

Функции асинхронного адаптера

Необходимо адаптировать стиль вызова при вызове синхронного кода из асинхронного контекста или наоборот. Для этого существуют две функции-адаптера из модуля asgiref.sync: async_to_sync() и sync_to_async(). Они используются для перехода между стилями вызова с сохранением совместимости.

Эти функции адаптера широко используются в Django. Сам пакет asgiref является частью проекта Django, и он автоматически устанавливается в качестве зависимости при установке Django с помощью pip.

async_to_sync()

async_to_sync(async_function, force_new_loop=False)

Принимает асинхронную функцию и возвращает синхронную функцию, которая обертывает ее. Может использоваться как прямая обертка или декоратор:

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 выполняется в цикле событий для текущего потока, если он существует. Если текущего цикла событий нет, создается новый цикл событий специально для единственного вызова async-функции и снова закрывается после его завершения. В любом случае функция async будет выполняться в потоке, отличном от вызывающего кода.

Значения Threadlocals и contextvars сохраняются через границу в обоих направлениях.

async_to_sync() - это, по сути, более мощная версия функции asyncio.run() из стандартной библиотеки Python. Помимо обеспечения работы потоковых локалей, она также включает режим thread_sensitive из sync_to_async(), когда эта обертка используется под ней.

sync_to_async()

sync_to_async(sync_function, thread_sensitive=True)[исходный код]

Принимает функцию синхронизации и возвращает асинхронную функцию, которая обертывает ее. Может использоваться как прямая обертка или декоратор:

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

Значения Threadlocals и contextvars сохраняются через границу в обоих направлениях.

Функции синхронизации обычно пишутся с учетом того, что все они выполняются в главном потоке, поэтому sync_to_async() имеет два режима работы с потоками:

  • thread_sensitive=True (по умолчанию): функция синхронизации будет выполняться в том же потоке, что и все остальные функции thread_sensitive. Это будет основной поток, если основной поток синхронный и вы используете обертку async_to_sync().
  • thread_sensitive=False: функция синхронизации будет запущена в совершенно новом потоке, который затем будет закрыт после завершения вызова.

Предупреждение

asgiref версия 3.3.0 изменила значение по умолчанию параметра thread_sensitive на True. Это более безопасное значение по умолчанию, и во многих случаях взаимодействия с Django это правильное значение, но не забудьте оценить использование sync_to_async() при обновлении asgiref с предыдущей версии.

Режим, чувствительный к потоку, довольно специфичен и выполняет много работы для запуска всех функций в одном потоке. Обратите внимание, однако, что он зависит от использования async_to_sync() выше него в стеке для правильного выполнения функций в главном потоке. Если вы используете asyncio.run() или подобное, он вернется к выполнению чувствительных к потоку функций в одном общем потоке, но это не будет основной поток.

В Django это необходимо потому, что многие библиотеки, в частности адаптеры баз данных, требуют, чтобы доступ к ним осуществлялся в том же потоке, в котором они были созданы. Также много существующего кода Django предполагает, что все выполняется в одном потоке, например, промежуточное ПО, добавляющее что-то в запрос для последующего использования в представлениях.

Вместо того, чтобы создавать потенциальные проблемы совместимости с этим кодом, мы решили добавить этот режим, чтобы весь существующий код синхронизации Django выполнялся в одном потоке и, таким образом, был полностью совместим с режимом async. Обратите внимание, что код синхронизации всегда будет находиться в отличном потоке от вызывающего его кода async, поэтому вам следует избегать передачи необработанных дескрипторов баз данных или других чувствительных к потоку ссылок.

Вернуться на верх