Spec-Zone.ru › Django 3.2

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

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.

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

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

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

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

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

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

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

Spec-Zone.ru

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