Spec-Zone.ru › Django 5.0

Асинхронная поддержка

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.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 защиты асинхронности для защиты данных от повреждения.

Декораторы

Новое в Django 5.0.

Следующие декораторы могут использоваться как с синхронными, так и с асинхронными функциями представлений:

  • 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().

Изменено в Django 4.2:

Добавлены асинхронные интерфейсы моделей и связанных менеджеров.

Производительность

При работе в режиме, не соответствующем представлению (например, асинхронное представление под WSGI или традиционное синхронное представление под ASGI), Django должен эмулировать другой стиль вызова, чтобы ваш код мог работать. Эта смена контекста приводит к небольшой потере производительности примерно в миллисекунду.

Это также относится к middleware. Django попытается минимизировать количество контекстных переключений между синхронным и асинхронным режимом. Если у вас сервер ASGI, но все middleware и представления синхронны, будет только одно переключение, прежде чем он войдёт в стек middleware.

Однако, если вы поместите синхронный middleware между сервером ASGI и асинхронным представлением, он должен перейти в синхронный режим для middleware и затем вернуться в асинхронный режим для представления. Django также удерживает синхронный поток открытым для обработки исключений middleware. Это может быть незаметно сначала, но добавление этой потери одного потока на запрос может устранить любое преимущество асинхронной производительности.

Вы должны провести собственные тесты производительности, чтобы увидеть, какое влияние имеет ASGI по сравнению с WSGI на ваш код. В некоторых случаях может быть увеличение производительности даже для чисто синхронного кода под ASGI, потому что код обработки запросов всё ещё работает асинхронно. В целом, вы захотите включить режим ASGI только если у вас есть асинхронный код в вашем проекте.

Обработка разъединений

Новое в Django 5.0.

Для долгоживущих запросов клиент может отключиться до того, как представление вернёт ответ. В этом случае в представлении будет поднято исключение 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) [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() без опоры на объект соединения в вызывающем коде.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.0/topics/async/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API