Асинхронная поддержка
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, чтобы защитить ваши данные от повреждения.
Запросы и 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 только в том случае, если в вашем проекте есть асинхронный код.
Асинхронная безопасность
-
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)[source]
Принимает синхронную функцию и возвращает асинхронную функцию, которая её обёртёт. Может использоваться как прямой обёрт или декоратор:
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() без обращения к объекту подключения в вызывающем коде.
Использование с фильтрами отчётов об исключениях
Предупреждение
Из-за механизмов, необходимых для пересечения границы синхронного/асинхронного, sync_to_async() и async_to_sync() не совместимы с sensitive_variables(), используемыми для маскировки локальных переменных в отчётах об исключениях.
Если вы используете эти адаптеры с конфиденциальными переменными, убедитесь, что проверили отчёт об исключении и рассмотрите возможность реализации настраиваемого фильтра, если это необходимо.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.2/topics/async/