event_loop 在 Django>=3.1 异步视图中存在多长时间

Posted

技术标签:

【中文标题】event_loop 在 Django>=3.1 异步视图中存在多长时间【英文标题】:How long does the event_loop live in a Django>=3.1 async view 【发布时间】:2020-12-10 13:33:28 【问题描述】:

我正在使用 Django 3.1 中的新异步视图。

我希望获得的一些好处是在视图已经提供其HttpResponse 之后执行一些简单的即发即弃的“任务”,例如发送推送通知或发送电子邮件。 我不是在寻找像 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")

我用 uvicorn 启动了 django 服务器。

当我向这个视图发出请求时,它会立即返回 HTTP 响应“非阻塞 HTTP 请求”。

与此同时,在 HTTP 响应之后,事件循环继续愉快地打印最多 200 的数字,然后打印“完成”。

这正是我想要用于我的即发即弃任务的行为。

很遗憾,我找不到有关运行此代码的事件循环的生命周期的任何信息。

事件循环的生命周期是多久? 有超时吗? 它取决于什么?在uvicorn上?这是可配置的吗?

有没有讨论这个话题的资源?

【问题讨论】:

【参考方案1】:

Django(不提供 ASGI 服务器)不限制事件循环的生命周期。

在这个问题的情况下:

ASGI 服务器:Uvicorn 事件循环:内置 asyncio 事件循环或uvloop(均不限制其生命周期)

对于 Uvicorn:

事件循环的生命周期有多长? 事件循环的生命周期与工作线程的生命周期一样长。

有超时吗? 不,任务一直运行到完成(除非工作人员没有响应并被 Gunicorn 杀死)。

它取决于什么?在 Uvicorn 上? Worker 的生命周期主要受限于--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 工作人员将在正常关机中等待您的任务。

【讨论】:

以上是关于event_loop 在 Django>=3.1 异步视图中存在多长时间的主要内容,如果未能解决你的问题,请参考以下文章

所有任务完成后如何终止python asyncio event_loop

c_cpp event_loop.cpp

aysncio

协程的使用

Django 从 3.2 -> 4.0 和 DRF 路径更新

django之创建第3个项目:编写第一个模板文件