Spec-Zone.ru › Django 3.1

Асинхронная поддержка

Новое в Django 3.0.

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

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

Изменено в Django 3.1:

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

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

Новое в Django 3.1.

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

Примечание

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

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

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

Если вы хотите использовать эти возможности, вам необходимо развернуть Django с помощью ASGI вместо WSGI.

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

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

Middleware можно создать для поддержки синхронных и асинхронных контекстов. Некоторые middleware Django построены таким образом, но не все. Чтобы увидеть, какой middleware Django необходимо адаптировать, вы можете включить отладку логирования для регистра django.request и поиска сообщений об ошибках типа «Синхронный middleware … адаптирован».

В режимах 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, которая все еще только синхронна, из асинхронного представления, вы активируете защиту Django от асинхронных ошибок, чтобы защитить ваши данные от повреждения.

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

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

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

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

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

Защита от асинхронных ошибок

DJANGO_ALLOW_ASYNC_UNSAFE

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

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

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

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

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

Если вы включите этот параметр, и существует одновременный доступ к асинхронно небезопасным частям 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(...):
    ...

Асинхронная функция выполняется в цикле событий текущего потока, если он есть. Если текущего цикла событий нет, создается новый цикл событий специально для одного асинхронного вызова и снова закрывается после завершения. В любом случае асинхронная функция будет выполняться в другом потоке по сравнению с вызывающим кодом.

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

async_to_sync() по сути является более мощной версией функции asyncio.run() в стандартной библиотеке Python. Помимо обеспечения работы threadlocals, она также включает режим 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(...):
    ...

Синхронные функции, как правило, написаны с предположением, что они все работают в главном потоке, поэтому 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 предполагает, что всё выполняется в одном потоке, например, middleware, добавляющий данные в запрос для последующего использования в представлении.

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/async/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API