Асинхронная поддержка
Django разрабатывает поддержку асинхронного (“async”) Python, но пока не поддерживает асинхронные представления или middleware; они появятся в будущих релизах.
Есть ограниченная поддержка других частей экосистемы async; в частности, Django может напрямую взаимодействовать с ASGI, и имеет некоторую поддержку асинхронной безопасности.
Безопасность при асинхронном выполнении
Некоторые ключевые части Django не могут безопасно работать в асинхронной среде, так как они имеют глобальное состояние, которое не учитывает корутины. Эти части Django классифицируются как «асинхронно небезопасные» и защищены от выполнения в асинхронной среде. ORM является основным примером, но есть и другие части, которые также защищены таким образом.
Если вы попытаетесь запустить какую-либо из этих частей из потока, в котором запущена event loop, вы получите ошибку SynchronousOnlyOperation. Обратите внимание, что вам не обязательно находиться непосредственно внутри асинхронной функции, чтобы эта ошибка произошла. Если вы вызвали синхронную функцию непосредственно из асинхронной функции без использования чего-либо вроде sync_to_async() или пула потоков, то она также может произойти, так как ваш код все еще выполняется в асинхронном контексте.
Если вы столкнулись с этой ошибкой, вам следует исправить свой код так, чтобы он не вызывал проблемную часть из асинхронного контекста; вместо этого напишите код, взаимодействующий с асинхронно небезопасным кодом в отдельной синхронной функции, а затем вызовите её с помощью asgiref.sync.sync_to_async() или любым другим предпочтительным способом выполнения синхронного кода в отдельном потоке.
Если вам абсолютно необходимо запустить этот код из асинхронного контекста — например, это навязано внешней средой, и вы уверены, что нет возможности одновременного выполнения (например, вы работаете в блокноте Jupyter), то вы можете отключить предупреждение с помощью переменной среды DJANGO_ALLOW_ASYNC_UNSAFE.
Предупреждение
Если вы включите этот параметр, и будет одновременный доступ к асинхронно небезопасным частям Django, вы можете столкнуться с потерей или повреждением данных. Будьте очень осторожны и не используйте это в производственных средах.
Если вам нужно сделать это внутри Python, сделайте это с помощью os.environ:
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
sync_function = async_to_sync(async_function)
@async_to_sync
async def async_function(...):
...
Асинхронная функция выполняется в event loop текущего потока, если он есть. Если текущий event loop отсутствует, создаётся новый event loop специально для асинхронной функции и закрывается после её завершения. В любом случае асинхронная функция будет выполняться в другом потоке по сравнению с вызывающим кодом.
Значения 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)
Оборачивает синхронную функцию и возвращает асинхронную (awaitable) функцию взамен. Может использоваться как прямой обёртчик, так и как декоратор:
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 предполагает, что всё выполняется в одном потоке (например, middleware добавляет вещи в запрос для последующего использования представлением).
Вместо того, чтобы вносить потенциальные проблемы совместимости в этот код, мы решили добавить этот режим, чтобы весь существующий синхронный код Django выполнялся в одном потоке и, таким образом, полностью соответствовал асинхронному режиму. Обратите внимание, что синхронный код всегда будет в другом потоке по сравнению с любым асинхронным кодом, вызывающим его, поэтому следует избегать передачи необработанных дескрипторов баз данных или других зависимых от потока ссылок в любом новом коде, который вы пишете.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.0/topics/async/