Spec-Zone.ru › Django 4.0

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

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

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

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

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

Асинхронный контекст может быть наложен на вас средой, в которой вы запускаете свой код Django. Например, блокноты Jupyter и интерактивные оболочки IPython прозрачно предоставляют активную очередь событий, чтобы было проще взаимодействовать с асинхронными API.

Если вы используете оболочку IPython, вы можете отключить эту очередь событий, выполнив:

%autoawait off

в качестве команды в командной строке IPython. Это позволит вам выполнить синхронный код без генерации ошибок SynchronousOnlyOperation; однако, вы также не сможете await асинхронные API. Чтобы включить очередь событий обратно, выполните:

%autoawait on

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

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

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

Spec-Zone.ru

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