Я играю с новыми асинхронными представлениями из 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, а затем печатать «готово».
Это именно то поведение, которое я хочу использовать для своих задач типа «выстрелил и забыл».
К сожалению, мне не удалось найти никакой информации о времени жизни цикла событий, в котором выполняется этот код.
Как долго живет цикл событий? Есть ли тайм-аут? От чего это зависит? На увикорне? Это настраивается?
Есть ли ресурсы, которые обсуждают эту тему?
Django (который не предоставляет сервер ASGI) не ограничивает время жизни цикла событий.
В случае этого вопроса:
uvloop
(оба не ограничивают свое время жизни)Для Увикорна:
Как долго живет цикл событий?
Цикл событий живет до тех пор, пока жив рабочий процесс.
Есть таймаут?
Нет, задачи выполняются до завершения (если рабочий не отвечает и не убит Гуникорном).
От чего это зависит? На Увикорне?
Срок службы рабочего в основном ограничен --limit-max-requests
(Gunicorn: --max-requests
).
Убедитесь в этом сами, указав --limit-max-requests 2
и дважды запустив представление:
uvicorn mysite.asgi:application --limit-max-requests 2
Независимо от того, насколько ограничено время жизни, нас может волновать то, как справиться с завершением работы.
Давайте посмотрим, как 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)
Давайте воспользуемся этим, добавив ваши задачи к задачам сервера.
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 будет ждать ваших задач в изящном завершении работы.