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

Новое в Django 3.0.

Django поддерживает запись асинхронных («асинхронных») представлений вместе со стеком запросов, полностью поддерживающим асинхронность, если вы работаете под 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 .

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

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

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

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

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

from asgiref.sync import sync_to_async

results = sync_to_async(Blog.objects.get)(pk=123)

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

from asgiref.sync import sync_to_async

@sync_to_async
def get_blog(pk):
    return Blog.objects.select_related('author').get(pk=pk)

Если вы случайно попытаетесь вызвать часть 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 = False )

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

from asgiref.sync import sync_to_async

async_function = sync_to_async(sync_function)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)

@sync_to_async
def sync_function(...):
    ...

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

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

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

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

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

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

Copyright ©2020 All rights reserved