Асинхронная поддержка
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.
Предупреждение
Вы получите преимущества полностью асинхронной стопки обработки запросов только если в вашем сайте нет загруженного синхронного 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 notebooks и 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.2/topics/async/