• 3.1
  • 3.2
  • 6.1
  • Версия документации: 5.0

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

Django поддерживает написание асинхронных («асинхронных») представлений, а также полностью асинхронный стек запросов, если вы работаете под управлением 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.

Предупреждение

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

Промежуточное программное обеспечение может быть создано для поддержки как синхронного, так и асинхронного контекста <async-middleware>`. Некоторые промежуточные программы Django построены таким образом, но не все. Чтобы увидеть, к какому промежуточному программному обеспечению Django нужно адаптироваться, вы можете включить ведение журнала отладки для регистратора django.request и поискать сообщения журнала о «Асинхронном обработчике, адаптированном для промежуточного программного обеспечения…».

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

Декораторы

New in Django 5.0.

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

Например:

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 поддерживается во всех наборах запросов (включая выходные данные 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 должен эмулировать другой стиль вызова, чтобы ваш код мог работать. Это переключение контекста приводит к небольшому снижению производительности, примерно на миллисекунду.

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

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

Вам следует провести собственное тестирование производительности, чтобы увидеть, какой эффект ASGI по сравнению с WSGI оказывает на ваш код. В некоторых случаях производительность может быть увеличена даже для чисто синхронной базы кода в рамках ASGI, поскольку весь код обработки запросов по-прежнему выполняется асинхронно. Обычно вам нужно включать режим ASGI только в том случае, если в вашем проекте есть асинхронный код.

Обработка отключений

New in 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. This will allow you to run synchronous code without generating SynchronousOnlyOperation errors; однако вы также не сможете «ожидать» асинхронные API. Чтобы снова включить цикл событий, выполните:

%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(). Они используются для перехода между стилями вызова при сохранении совместимости.

Эти функции адаптера широко используются в 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(): ...

Функция 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 выполнялся в одном потоке и, таким образом, был полностью совместим с асинхронным режимом. Обратите внимание, что код синхронизации всегда будет находиться в различном потоке по сравнению с любым асинхронным кодом, который его вызывает, поэтому вам следует избегать передачи необработанных дескрипторов базы данных или других чувствительных к потоку ссылок.

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

Back to Top