Как долго event_loop живет в асинхронном представлении Django >= 3.1

Я играю с новыми асинхронными представлениями из Django 3.1.

Некоторые преимущества, которые я хотел бы иметь, — это выполнять несколько простых «задач» «запустить и забыть» после того, как представление уже дало свое HttpResponse, например, отправить push-уведомление или отправить электронное письмо. Я не ищу решения со сторонними пакетами типа celery!

Чтобы протестировать эти асинхронные представления, я использовал код из этого руководства: https://testdriven.io/blog/django-async-views/

async def http_call_async():
    for num in range(1, 200):
        await asyncio.sleep(1)
        print(num)
        
    # TODO: send email async
    print('done')


async def async_view(request):
    loop = asyncio.get_event_loop()
    loop.create_task(http_call_async())
    return HttpResponse("Non-blocking HTTP request")

Я запустил сервер django с помощью uvicorn.

Когда я делаю запрос к этому представлению, он немедленно возвращает HTTP-ответ «Неблокирующий HTTP-запрос».

Тем временем и после HTTP-ответа цикл событий радостно продолжает печатать числа до 200, а затем печатать «готово».

Это именно то поведение, которое я хочу использовать для своих задач типа «выстрелил и забыл».

К сожалению, мне не удалось найти никакой информации о времени жизни цикла событий, в котором выполняется этот код.

Как долго живет цикл событий? Есть ли тайм-аут? От чего это зависит? На увикорне? Это настраивается?

Есть ли ресурсы, которые обсуждают эту тему?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
9
0
859
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Django (который не предоставляет сервер ASGI) не ограничивает время жизни цикла событий.

В случае этого вопроса:

  • Сервер ASGI: Uvicorn
  • Цикл событий: либо встроенный цикл событий asyncio, либо uvloop (оба не ограничивают свое время жизни)

Для Увикорна:

  • Как долго живет цикл событий?
    Цикл событий живет до тех пор, пока жив рабочий процесс.

  • Есть таймаут?
    Нет, задачи выполняются до завершения (если рабочий не отвечает и не убит Гуникорном).

  • От чего это зависит? На Увикорне?
    Срок службы рабочего в основном ограничен --limit-max-requests (Gunicorn: --max-requests).

    • Это настраивается?
      Да, но рабочий все равно выходит в какой-то момент. Он также может быть убит по другим причинам.

Убедитесь в этом сами, указав --limit-max-requests 2 и дважды запустив представление:

uvicorn mysite.asgi:application --limit-max-requests 2

Изящное завершение работы

Независимо от того, насколько ограничено время жизни, нас может волновать то, как справиться с завершением работы.

Как Uvicorn корректно завершает работу?

Давайте посмотрим, как Uvicorn worker (uvicorn.server.Server) корректно завершает работу по запросам.

uvicorn.protocols.http.h11_impl.H11Protocol#__init__: Хранить ссылку на задачи сервера

# Shared server state
self.server_state = server_state
self.tasks = server_state.tasks

uvicorn.protocols.http.h11_impl.H11Protocol#handle_events: Добавить задачу запроса к задачам

self.cycle = RequestResponseCycle(
    ...
)
task = self.loop.create_task(self.cycle.run_asgi(app))
task.add_done_callback(self.tasks.discard)
self.tasks.add(task)

uvicorn.server.Server#shutdown: Дождитесь завершения существующих задач

if self.server_state.tasks and not self.force_exit:
    msg = "Waiting for background tasks to complete. (CTRL+C to force quit)"
    logger.info(msg)
    while self.server_state.tasks and not self.force_exit:
        await asyncio.sleep(0.1)

Как заставить Uvicorn worker ждать выполнения вашей задачи?

Давайте воспользуемся этим, добавив ваши задачи к задачам сервера.

async def async_view(request):
    loop = asyncio.get_event_loop()
    # loop.create_task(http_call_async())       # Replace this
    task = loop.create_task(http_call_async())  # with this
    server_state = await get_server_state()             # Add this
    task.add_done_callback(server_state.tasks.discard)  # Add this
    server_state.tasks.add(task)                        # Add this
    return HttpResponse("Non-blocking HTTP request")
import gc
from uvicorn.server import ServerState

_server_state = None


@sync_to_async
def get_server_state():
    global _server_state
    if not _server_state:
        objects = gc.get_objects()
        _server_state = next(o for o in objects if isinstance(o, ServerState))
    return _server_state

Теперь рабочий Uvicorn будет ждать ваших задач в изящном завершении работы.

Другие вопросы по теме