我想创建一个第三方聊天机器人API,该API是异步的,并在10秒钟的暂停后回复“确定”。
import time
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
return wait()
我尝试了如下所示的芹菜,其中我正在等待芹菜响应以查看自身:
import time
from celery import shared_task
@shared_task
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
a = wait.delay()
work = AsyncResult(a.id)
while True:
if work.ready():
return work.get(timeout=1)
但是此解决方案是同步工作的,没有区别。在不要求用户继续请求直到收到结果之前,我们如何才能使其异步?
检出Django 3 ASGI(异步服务器网关接口)支持:https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
如@Blusky的答案中所述:异步API将存在于Django 3.X中。 不早于。
如果这不是一个选择,那么答案就是no。
还请注意,即使使用django 3.X,访问数据库的任何django代码也将是not异步的,它必须在线程(线程池)中执行]
Celery适用于后台任务或延迟任务,但是celery不会返回HTTP响应,因为它没有收到应响应的HTTP请求。芹菜也不友好。
您可能不得不考虑更改架构/实现。查看您的总体问题,并问自己是否真的需要使用Django的异步API。
此API是用于浏览器应用程序还是用于机器对机器的应用程序?
您的客户可以使用Web套接字并等待答案吗?
服务器端的您能否分开阻塞部分和非阻塞部分?将django用于所有不阻塞的事物,所有周期性/延迟的事物(django + celelry),并使用Web服务器插件或python ASGI代码或Web套接字实现异步部分。
一些想法
使用Django + nginx nchan(如果您的Web服务器是celery)
链接到nchan:https://nchan.io/您的API调用将创建一个任务ID,启动一个celery任务,立即返回该任务ID或一个查询URL。
例如,轮询URL将通过nchan long轮询通道进行处理。您的客户端连接到与nchan长轮询通道相对应的url,并且每当您完成任务(十秒结束)时,celery就会对其进行阻止
使用Django + ASGI服务器+一个手工编码的视图,并使用类似的策略来替代nginx nchan
与上述逻辑相同,但是您不使用nginx nchan,而是自己实现
使用ASGI服务器+非阻塞框架(或仅使用一些手工编码的ASGI视图)来处理所有阻塞的URL,其余使用Django。
他们可能通过数据库,本地文件或本地http请求交换数据。
只是保持阻塞并在服务器上抛出足够的工作进程/线程
这可能是最糟糕的建议,但是如果仅用于个人用途,并且您知道并行将有多少个请求,然后只需确保您有足够的Django worker,这样您就可以承受阻塞了。在这种情况下,您将为每个低请求阻止整个Django工作者。
使用websockets。例如与Django的channels模块一起使用]
Websockets可以使用带有django通道模块(pip install channels
)(https://github.com/django/channels)的早期版本的django(> = 2.2)实现
您需要一个ASGI服务器来服务异步部分。您可以使用例如Daphne ot uvicorn(频道文档对此进行了很好的解释)
附录2020-06-01:调用同步Django代码的简单异步示例
以下代码使用starlette模块,因为它看起来非常简单和很小
miniasyncio.py
import asyncio import concurrent.futures import os import django from starlette.applications import Starlette from starlette.responses import Response from starlette.routing import Route os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pjt.settings') django.setup() from django_app.xxx import synchronous_func1 from django_app.xxx import synchronous_func2 executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) async def simple_slow(request): """ simple function, that sleeps in an async matter """ await asyncio.sleep(5) return Response('hello world') async def call_slow_dj_funcs(request): """ slow django code will be called in a thread pool """ loop = asyncio.get_running_loop() future_func1 = executor.submit(synchronous_func1) func1_result = future_func1.result() future_func2 = executor.submit(synchronous_func2) func2_result = future_func2.result() response_txt = "OK" return Response(response_txt, media_type="text/plain") routes = [ Route("/simple", endpoint=simple_slow), Route("/slow_dj_funcs", endpoint=call_slow_dj_funcs), ] app = Starlette(debug=True, routes=routes)
例如,您可以使用以下代码运行此代码
pip install uvicorn uvicorn --port 8002 miniasyncio:app
然后在ypur Web服务器上将这些特定的URL路由到uvicorn,而不是django应用程序服务器。
[最佳选择是使用futurasync
API,它将在Django 3.1版中提出(已经在alpha中提供)