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

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

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

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

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

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

Новое в Django 3.1.

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

Примечание

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

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

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

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

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

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

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

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

Copyright ©2021 All rights reserved