Множественные одновременные запросы в асинхронных представлениях Django

Начиная с версии 3.1 Django поддерживает асинхронные представления. У меня есть приложение Django, работающее на uvicorn. Я пытаюсь написать асинхронное представление, которое может обрабатывать несколько запросов к себе одновременно, но безуспешно.

Частые примеры, которые я видел, включают выполнение нескольких медленных операций ввода/вывода изнутри представления:

async def slow_io(n, result):
    await asyncio.sleep(n)
    return result


async def my_view(request, *args, **kwargs):
    task_1 = asyncio.create_task(slow_io(3, 'Hello'))
    task_2 = asyncio.create_task(slow_io(5, 'World'))
    result = await task_1 + await task_2
    return HttpResponse(result)

Это даст нам "HelloWorld" через 5 секунд вместо 8, потому что запросы выполняются параллельно.

Что я хочу - это обрабатывать несколько запросов к my_view одновременно.

async def slow_io(n, result):
    await asyncio.sleep(n)
    return result

async def my_view(request, *args, **kwargs):
    result = await slow_io(5, 'result')
    return HttpResponse(result)

Я ожидаю, что этот код обработает 2 одновременных запроса за 5 секунд, но это происходит за 10.

Что я упускаю? Возможно ли это в Django?

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

Для асинхронного запуска всех задач вам просто необходимо использовать функции asyncio, такие как gather:

import asyncio

async def slow_io(n, result):
    await asyncio.sleep(n)
    return result


async def my_view(request, *args, **kwargs):
    all_tasks_result = await asyncio.gather(slow_io(3, 'Hello '), slow_io(5, "World"))
    result = "".join(all_tasks_result)
    return HttpResponse(result)

Вот пример проекта на Django 3.2 с несколькими асинхронными представлениями и тестами. Я тестировал его несколькими способами:

  • Запросы от тестового клиента Django обрабатываются одновременно, как и ожидалось.
  • Запросы к различным представлениям от одного клиента обрабатываются одновременно, как и ожидалось.
  • Запросы к одному и тому же представлению от разных клиентов обрабатываются одновременно, как и ожидалось.

Что не работает так, как ожидалось?

  • Запросы к одному и тому же представлению от одного клиента обрабатываются по одному за раз, а я этого не ожидал.

В Django doc:

есть предупреждение.

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

Программное обеспечение промежуточного слоя может быть построено для поддержки как синхронных, так и асинхронных контекстов. Некоторые из промежуточных программ Django построены таким образом, но не все. Чтобы увидеть, какие промежуточные программы Django должны быть адаптированы, вы можете включить отладочную регистрацию для логгера django.request и искать сообщения в журнале о "Synchronous middleware ... adapted".

.

Таким образом, проблема может быть вызвана каким-то промежуточным ПО синхронизации даже в "голом" Django. Но там также говорится, что Django должен создавать поток на каждый запрос в этом случае.

Мое лучшее предположение, что это связано с соединением клиент-сервер, а не со стеком sync или async. Несмотря на то, что Google говорит, что браузеры создают новые соединения для каждой вкладки по соображениям безопасности, я думаю:

  • Браузер может сохранять одно и то же соединение для нескольких вкладок, если url одинаковый по причинам экономии.
  • Django создает асинхронные задачи или потоки для каждого соединения, а не для каждого запроса, как утверждается.

Нужно проверить.

Вернуться на верх