Django 异步视图中的多个并发请求

Posted

技术标签:

【中文标题】Django 异步视图中的多个并发请求【英文标题】:Multiple concurrent requests in Django async views 【发布时间】:2022-01-23 03:58:20 【问题描述】:

从 3.1 版起,Django 支持异步视图。我有一个在 uvicorn 上运行的 Django 应用程序。我正在尝试编写一个异步视图,它可以同时处理对自身的多个请求,但没有成功。

我见过的常见示例包括从视图内部进行多个慢速 I/O 操作:

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)

这将在 5 秒而不是 8 秒后为我们生成“HelloWorld”,因为请求是同时运行的。

我想要的是同时处理对 my_view 的多个请求。例如。我希望这段代码能在 5 秒内同时处理 2 个请求,但需要 10 秒。

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)

我用这个命令运行 uvicorn:

uvicorn --host 0.0.0.0 --port 8000 main.asgi:application --reload

Django doc 说:

主要好处是能够在不使用 Python 线程的情况下为数百个连接提供服务。

所以有可能。

我错过了什么?

更新: 看来,我的测试设置是错误的。我在浏览器中打开多个标签并一次刷新它们。详情请见我的answer。

【问题讨论】:

如何发送请求? @lucutzu33 我在浏览器中打开多个选项卡并一次全部刷新。这并不精确,但大超时(5、10 秒)足以看出选项卡加载之间的延迟比刷新它们所需的时间长得多。我还添加了时间戳记录,它显示请求之间的准确时间为 5 秒。 【参考方案1】:

您的问题是您编写的代码类似于同步版本。你等待每个函数的结果,只有在这之后,你才等待下一个函数。

你只需要使用像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)

【讨论】:

请检查我的answer。您描述了问题中的第一个案例,它按预期工作。但我说的是第二种情况 - 视图中的单个异步任务,我必须等待,才能将结果放入响应中。以及对该视图的多个同时请求,我希望同时处理这些请求。它似乎与同步或异步堆栈无关,而是我如何测试它以及浏览器和 Django 的工作方式。【参考方案2】:

这里是 Django 3.2 上的 sample project,带有多个异步视图和测试。我以多种方式对其进行了测试:

来自 Django 测试客户端的请求按预期同时处理。 按预期同时处理来自单个客户端的对不同视图的请求。 按预期同时处理来自不同客户端的对同一视图的请求。

什么没有按预期工作?

单个客户端对同一视图的请求一次由一个客户端处理,我没想到会这样。

Django中有一个警告doc:

只有在您的站点中没有加载同步中间件时,您才能获得完全异步请求堆栈的好处。如果有一个同步中间件,那么 Django 必须为每个线程使用一个线程请求为其安全地模拟同步环境。

可以构建中间件来支持同步和异步上下文。 Django 的一些中间件是这样构建的,但不是全部。 要查看 Django 必须适应哪些中间件,您可以打开 django.request 记录器的调试日志记录并查找有关“同步中间件……已适应”的日志消息.

因此,即使在裸 Django 中,也可能是一些同步中间件导致问题。但它也指出 Django 在这种情况下应该使用线程,而我没有使用任何同步中间件,只使用标准的。

我的最佳猜测是,它与客户端-服务器连接有关,与同步或异步堆栈无关。尽管 Google 表示出于安全原因,浏览器会为每个选项卡创建新连接,但我认为:

如果出于经济原因,URL 相同,浏览器可能会为多个选项卡保持相同的连接。 如它所述,Django 会根据连接而不是根据请求创建异步任务或线程。

需要检查一下。

【讨论】:

以上是关于Django 异步视图中的多个并发请求的主要内容,如果未能解决你的问题,请参考以下文章

js 多个异步 的并发控制

JavaScript异步并发请求问题

在 Django 视图中处理多个 POST 请求

如何在 Django 中异步发送 API 请求过程?

如何从 Django 发送异步 HTTP 请求并在 python2.7 中等待结果?

如何在 Django 中处理并发请求?