Асинхронная поддержка¶
Django поддерживает написание асинхронных («асинхронных») представлений, а также полностью асинхронный стек запросов, если вы работаете под управлением ASGI. Асинхронные представления по-прежнему будут работать под WSGI, но с потерями производительности и без возможности эффективно выполнять длительные запросы.
Мы все еще работаем над асинхронной поддержкой ORM и других частей Django. Вы можете ожидать увидеть это в будущих выпусках. На данный момент вы можете использовать адаптер sync_to_async() для взаимодействия с частями синхронизации Django. Существует также целый ряд асинхронных библиотек Python, с которыми вы можете интегрироваться.
Support for async views was added.
Асинхронные представления¶
Any view can be declared async by making the callable part of it return a
coroutine - commonly, this is done using async def. For a function-based
view, this means declaring the whole view using async def. For a
class-based view, this means making its __call__() method an async def
(not its __init__() or as_view()).
Примечание
Django uses asyncio.iscoroutinefunction to test if your view is
asynchronous or not. If you implement your own method of returning a
coroutine, ensure you set the _is_coroutine attribute of the view
to asyncio.coroutines._is_coroutine so this function returns True.
На сервере WSGI асинхронные представления будут выполняться в собственном одноразовом цикле событий. Это означает, что вы можете без проблем использовать асинхронные функции, такие как одновременные асинхронные HTTP-запросы, но вы не получите преимуществ асинхронного стека.
Основным преимуществом является возможность обслуживать сотни соединений без использования потоков Python. Это позволяет использовать медленную потоковую передачу, длительный опрос и другие интересные типы ответов.
Если вы хотите их использовать, вам нужно будет вместо этого развернуть Django с помощью ASGI.
Предупреждение
Вы получите преимущества полностью асинхронного стека запросов только в том случае, если на ваш сайт не загружено синхронное промежуточное программное обеспечение. Если есть часть синхронного промежуточного программного обеспечения, то Django должен использовать поток для каждого запроса, чтобы безопасно эмулировать для него синхронную среду.
Middleware can be built to support both sync and async contexts. Some of Django’s middleware is built like
this, but not all. To see what middleware Django has to adapt, you can turn
on debug logging for the django.request logger and look for log
messages about «Synchronous middleware … adapted».
И в режиме ASGI, и в режиме WSGI вы по-прежнему можете безопасно использовать асинхронную поддержку для одновременного, а не последовательного выполнения кода. Это особенно удобно при работе с внешними API или хранилищами данных.
If you want to call a part of Django that is still synchronous, like the ORM,
you will need to wrap it in a sync_to_async() call. For example:
from asgiref.sync import sync_to_async
results = await sync_to_async(Blog.objects.get, thread_sensitive=True)(pk=123)
You may find it easier to move any ORM code into its own function and call that
entire function using sync_to_async(). For example:
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)
If you accidentally try to call a part of Django that is still synchronous-only from an async view, you will trigger Django’s asynchronous safety protection to protect your data from corruption.
Производительность¶
При работе в режиме, который не соответствует представлению (например, асинхронное представление в 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() (или любой другой способ запуска кода синхронизации в отдельном потоке).
Асинхронный контекст может быть навязан вам средой, в которой вы выполняете код Django. Например, блокноты Jupyter и интерактивные оболочки IPython прозрачно обеспечивают активный цикл событий, что упрощает взаимодействие с асинхронными API.
If you’re using an IPython shell, you can disable this event loop by running:
%autoawait off
as a command at the IPython prompt. This will allow you to run synchronous code
without generating SynchronousOnlyOperation
errors; however, you also won’t be able to await asynchronous APIs. To turn
the event loop back on, run:
%autoawait on
Если вы находитесь в среде, отличной от IPython (или по какой-то причине вы не можете отключить автоожидание в 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(). Они используются для перехода между стилями вызова при сохранении совместимости.
These adapter functions are widely used in Django. The asgiref package
itself is part of the Django project, and it is automatically installed as a
dependency when you install Django with 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_sensitivity для 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_sensitivity=True(по умолчанию): функция синхронизации будет выполняться в том же потоке, что и все другиеthread_sensitivityфункции. Это будет основной поток, если основной поток синхронный и вы используете оболочкуasync_to_sync().thread_sensitivity=False: функция синхронизации будет выполняться в совершенно новом потоке, который затем закрывается после завершения вызова.
Предупреждение
В asgiref версии 3.3.0 значение по умолчанию параметра thread_sensitivity изменено на True. Это более безопасное значение по умолчанию, и во многих случаях взаимодействия с Django это правильное значение, но обязательно оцените использование sync_to_async() при обновлении asgiref из предыдущей версии.
Поточно-зависимый режим является совершенно особенным и выполняет большую работу по запуску всех функций в одном потоке. Однако обратите внимание, что он полагается на использование async_to_sync() над ним в стеке для правильного выполнения операций в основном потоке. Если вы используете asyncio.run() или аналогичный, он вернется к выполнению потокозависимых функций в одном общем потоке, но это не будет основной поток.
Причина, по которой это необходимо в Django, заключается в том, что многие библиотеки, в частности адаптеры баз данных, требуют, чтобы к ним обращались в том же потоке, в котором они были созданы. Кроме того, большая часть существующего кода Django предполагает, что все они выполняются в одном потоке, например промежуточное программное обеспечение, добавляющее данные в запрос для последующего использования в представлениях.
Вместо того, чтобы создавать потенциальные проблемы совместимости с этим кодом, мы решили добавить этот режим, чтобы весь существующий код синхронизации Django выполнялся в одном потоке и, таким образом, был полностью совместим с асинхронным режимом. Обратите внимание, что код синхронизации всегда будет находиться в различном потоке по сравнению с любым асинхронным кодом, который его вызывает, поэтому вам следует избегать передачи необработанных дескрипторов базы данных или других чувствительных к потоку ссылок.