Асинхронная поддержка
Django поддерживает асинхронные («async») представления, а также полностью асинхронную обработку запросов, если вы используете ASGI. Асинхронные представления также будут работать с WSGI, но с потерей производительности и без возможности эффективной обработки длительных запросов.
Мы продолжаем работу над асинхронной поддержкой ORM и других частей Django. Ожидайте этого в будущих релизах. Пока вы можете использовать адаптер sync_to_async() для взаимодействия с синхронными частями Django. Также есть ряд асинхронных Python-библиотек, которые вы можете интегрировать.
Асинхронные представления
Любое представление можно объявить асинхронным, сделав вызываемый фрагмент возвращающим корутину — обычно это делается с помощью async def. Для представления на основе функций это означает объявление всего представления с помощью async def. Для представления на основе класса это означает объявление обработчиков HTTP-методов, таких как get() и post(), как async def (а не его __init__(), или as_view()).
Примечание
Django использует asgiref.sync.iscoroutinefunction для проверки, является ли ваше представление асинхронным. Если вы реализуете свой собственный метод возврата корутины, убедитесь, что используете asgiref.sync.markcoroutinefunction, чтобы эта функция возвращала True.
В серверной среде WSGI асинхронные представления будут выполняться в собственной, одноразовой очереди событий. Это означает, что вы можете использовать асинхронные возможности, такие как одновременные асинхронные HTTP-запросы, без проблем, но не получите преимущества асинхронной обработки.
Основные преимущества — возможность обслуживания сотен подключений без использования потоков Python. Это позволяет использовать медленный стримминг, длительный опрос и другие интересные типы ответов.
Для использования этих возможностей вам необходимо развернуть Django с помощью ASGI вместо WSGI.
Предупреждение
Вы получите преимущества полностью асинхронной обработки запросов только в том случае, если в вашем сайте нет загруженного синхронного middleware. Если есть фрагмент синхронного middleware, Django должен использовать один поток на запрос для безопасного эмуляции синхронной среды для него.
Middleware можно создать, чтобы поддерживать синхронные и асинхронные контексты. Некоторые middleware Django созданы таким образом, но не все. Чтобы увидеть, какой middleware Django нужно адаптировать, включите журналирование отладки для логгера django.request и посмотрите на сообщения в логах о «Асинхронный обработчик адаптирован для middleware …».
В режимах ASGI и WSGI вы все равно можете безопасно использовать асинхронную поддержку для одновременного выполнения кода, а не последовательно. Это особенно полезно при работе с внешними API или хранилищами данных.
Если вам нужно вызвать часть Django, которая все еще синхронна, вам нужно обернуть ее в вызов sync_to_async(). Например:
from asgiref.sync import sync_to_async results = await sync_to_async(sync_function, thread_sensitive=True)(pk=123)
Если вы случайно попытаетесь вызвать часть Django, которая работает только в синхронном режиме, из асинхронного представления, вы активируете механизм защиты асинхронной безопасности Django, чтобы защитить ваши данные от повреждения.
Декораторы
Следующие декораторы можно использовать как с синхронными, так и с асинхронными функциями представлений:
cache_control()never_cache()no_append_slash()csrf_exempt()csrf_protect()ensure_csrf_cookie()requires_csrf_token()sensitive_variables()sensitive_post_parameters()gzip_page()condition()conditional_page()etag()last_modified()require_http_methods()require_GET()require_POST()require_safe()vary_on_cookie()vary_on_headers()xframe_options_deny()xframe_options_sameorigin()xframe_options_exempt()
Например:
from django.views.decorators.cache import never_cache @never_cache def my_sync_view(request): ... @never_cache async def my_async_view(request): ...
Запросы и ORM
В некоторых случаях Django может выполнять запросы ORM асинхронно:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
Подробные заметки можно найти в Асинхронные запросы, но коротко:
- Все
QuerySetметоды, вызывающие SQL-запрос, имеют асинхронный вариант с префиксомa. -
async forподдерживается для всех QuerySet (включая результатvalues()иvalues_list()).
Django также поддерживает некоторые асинхронные методы модели, использующие базу данных:
async def make_book(*args, **kwargs):
book = Book(...)
await book.asave(using="secondary")
async def make_book_with_tags(tags, *args, **kwargs):
book = await Book.objects.acreate(...)
await book.tags.aset(tags)
Транзакции пока не работают в асинхронном режиме. Если вам нужен функционал транзакций, рекомендуется написать этот фрагмент кода как отдельную синхронную функцию и вызвать ее с помощью sync_to_async().
Производительность
При выполнении в режиме, не соответствующем представлению (например, асинхронное представление в WSGI или традиционное синхронное представление в ASGI), Django должен эмулировать другой стиль вызова, чтобы ваш код мог работать. Эта смена контекста приводит к небольшой потере производительности примерно в миллисекунду.
Это также относится к middleware. Django попытается свести к минимуму количество переключений между синхронным и асинхронным режимом. Если у вас ASGI-сервер, а все ваши middleware и представления синхронные, будет одно переключение перед входом в стек middleware.
Однако, если вы поместите синхронный middleware между ASGI-сервером и асинхронным представлением, ему придется переключиться в синхронный режим для middleware, а затем обратно в асинхронный режим для представления. Django также удерживает синхронный поток открытым для обработки исключений middleware. Это может быть незаметно с самого начала, но добавление такой пенализации (один поток на запрос) может устранить все преимущества асинхронной производительности.
Вы должны самостоятельно провести тестирование производительности, чтобы увидеть, какое влияние ASGI или WSGI оказывает на ваш код. В некоторых случаях может произойти повышение производительности даже для чисто синхронного кода в ASGI, поскольку код обработки запросов все еще выполняется асинхронно. В целом, вы хотите включить режим ASGI только в том случае, если в вашем проекте есть асинхронный код.
Обработка разъединений
Для долгоживущих запросов клиент может отключиться до того, как представление вернёт ответ. В этом случае в представлении будет поднято исключение asyncio.CancelledError. Вы можете перехватить эту ошибку и обработать её, если вам нужно выполнить какие-либо действия по очистке:
async def my_view(request):
try:
# Do some work
...
except asyncio.CancelledError:
# Handle disconnect
raise
Вы также можете обрабатывать отключения клиентов в потоковых ответах.
Асинхронная безопасность
-
DJANGO_ALLOW_ASYNC_UNSAFE
Определённые ключевые части Django не могут безопасно работать в асинхронной среде, так как они имеют глобальное состояние, которое не учитывает корутины. Эти части Django классифицируются как «асинхронно небезопасные» и защищены от выполнения в асинхронной среде. ORM является основным примером, но существуют и другие защищённые части.
Если вы попытаетесь запустить любой из этих фрагментов из потока, где работает цикл обработки событий, вы получите ошибку SynchronousOnlyOperation. Обратите внимание, что для возникновения этой ошибки вам не обязательно быть внутри асинхронной функции напрямую. Если вы вызвали синхронную функцию непосредственно из асинхронной функции, не используя sync_to_async() или аналогичную функцию, то ошибка также может возникнуть. Это происходит потому, что ваш код всё ещё выполняется в потоке с активным циклом обработки событий, даже если он не объявлен как асинхронный код.
Если вы столкнулись с этой ошибкой, вам необходимо исправить свой код, чтобы не вызывать проблемный код из асинхронного контекста. Вместо этого напишите свой код, взаимодействующий с небезопасными для асинхронности функциями, в своей собственной синхронной функции, и вызовите её с помощью asgiref.sync.sync_to_async() (или любым другим способом выполнения синхронного кода в отдельном потоке).
Асинхронный контекст может быть наложен на ваш код средой, в которой вы выполняете свой код Django. Например, блокноты Jupyter и интерактивные оболочки IPython прозрачно предоставляют активный цикл обработки событий, чтобы взаимодействие с асинхронными API было проще.
Если вы используете интерактивную оболочку IPython, вы можете отключить этот цикл обработки событий, выполнив:
%autoawait off
в качестве команды в командной строке IPython. Это позволит вам запускать синхронный код без генерации ошибок SynchronousOnlyOperation; однако, вы также не сможете await асинхронные API. Чтобы снова включить цикл обработки событий, выполните:
%autoawait on
Если вы работаете в среде, отличной от IPython (или вы не можете отключить autoawait в 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(). Они используются для перехода между стилями вызова, сохраняя совместимость.
Эти функции-адаптеры широко используются в 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(): ...
Значения 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 предполагает, что всё выполняется в одном потоке, например, middleware добавляет вещи в запрос для последующего использования в представлениях.
Вместо того, чтобы вносить потенциальные проблемы совместимости с этим кодом, мы вместо этого решили добавить этот режим, чтобы весь существующий синхронный код Django выполнялся в одном потоке и, таким образом, был полностью совместим с асинхронным режимом. Обратите внимание, что синхронный код всегда будет в различном потоке по сравнению с любым асинхронным кодом, который его вызывает, поэтому вы должны избегать передачи необработанных дескрипторов базы данных или других чувствительных к потокам ссылок.
На практике это ограничение означает, что вы не должны передавать особенности объекта базы данных connection при вызове sync_to_async(). Это вызовет проверки безопасности потоков:
# DJANGO_SETTINGS_MODULE=settings.py python -m asyncio >>> import asyncio >>> from asgiref.sync import sync_to_async >>> from django.db import connection >>> # In an async context so you cannot use the database directly: >>> connection.cursor() django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async. >>> # Nor can you pass resolved connection attributes across threads: >>> await sync_to_async(connection.cursor)() django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread can only be used in that same thread. The object with alias 'default' was created in thread id 4371465600 and this is thread id 6131478528.
Вместо этого вы должны заключить весь доступ к базе данных в вспомогательную функцию, которую можно вызвать с sync_to_async() без обращения к объекту подключения в вызывающем коде.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/topics/async/