Django 3.1中的异步视图

2020-09-10 03:05:38

编写异步代码使您能够轻而易举地加快应用程序的速度。随着Django 3.1终于支持异步视图、中间件和测试,现在是让它们进入你的腰带的绝佳时机。

如果您有兴趣更多地了解异步代码背后的强大功能,以及Python中线程、多处理和异步之间的区别,请查看我的使用并发、并行和异步POST加速Python。

只要您已经熟悉Django本身,向非基于类的视图添加异步功能就非常简单。

ASGI代表异步服务器网关接口。它是WSGI的现代异步后续产品,为创建基于Python的异步Web应用程序提供了标准。

另外值得一提的是,ASGI向后兼容WSGI,即使您还没有准备好切换到编写异步应用程序,这也是从Gunicorn或uWSGI这样的WSGI服务器切换到Uvicorn或Daphne这样的ASGI服务器的好借口。

$mkdir django-async-views&;&;cd django-async-views$python3.8-m venv env$source env/bin/activate(Env)$pip install Django(Env)$django-admin.py startproject hello_async。

如果您正在使用内置的开发服务器,Django将运行您的异步视图,但是它实际上不会异步运行这些视图,所以我们将使用Uvicorn来设置服务器。

要使用Uvicorn运行项目,请从项目的根目录使用以下命令:

接下来,让我们创建第一个异步视图。在";hello_async";文件夹中添加一个新文件以保存您的视图,然后添加以下视图:

在Django中创建异步视图与创建同步视图一样简单--您只需添加Async关键字即可。

#hello_async/urls.py from django.contrib import admin from django.urls import path from hello_async.views import index urlpattern=[path(";admin/";,admin.。站点。URL),路径(";";,索引),]。

--reload标志告诉uvicorn监视您的文件是否有更改,如果发现更改则重新加载。这可能是不言而喻的。

这不是世界上最令人兴奋的事情,但是,嘿,这是一个开始。值得注意的是,使用Django的内置开发服务器运行此视图将产生完全相同的功能和输出。这是因为我们实际上并没有在处理程序中执行任何异步操作。

值得注意的是,异步支持是完全向后兼容的,因此您可以混合异步和同步视图、中间件和测试。Django将在正确的执行上下文中执行每个命令。

#hello_async/views.py从Time导入asyncio导入睡眠从django.http导入httpx。http import HttpResponse#helpers async def http_call_async():对于范围(1,6)内的数字:等待异步。睡眠(1)使用httpx异步打印(编号)。作为客户端的AsyncClient():r=等待客户端。GET(";https://httpbin.org/";)Print(R)def http_call_sync():对于范围(1,6)中的数字:休眠(1)打印(数字)r=httpx。GET(";https://httpbin.org/";)Print(R)#Views异步定义索引(请求):return HTTPResponse(";Hello,Async Django!";)Async Def Async_VIEW(Request):LOOP=Asyncio。Get_event_loop()循环。Create_task(http_call_async())return HttpResponse(";非阻塞HTTP请求";)def sync_view(Request):http_call_sync()return HttpResponse(";阻塞HTTP请求";)。

#hello_async/urls.py from django.contrib从django.urls导入admin来自hello_async.views导入索引,async_view,sync_view urlpattern=[path(";admin/";,admin.。站点。URL)、路径(";异步/";,异步视图)、路径(";同步/";,同步视图)、路径(";";,索引),]

在这里,HTTP响应是在循环和对https://httpbin.org/的请求完成之后发送的。

#hello_async/urls.py from django.contrib从django.urls导入admin来自hello_async.views导入索引,async_view,sync_view,Smoke_ome_meats urlpattern=[path(";admin/";,admin.。站点。URL)、路径(";Smoke_Some_Meats/";,Smoke_Some_Meats)、路径(";Async/";,Async_view)、路径(";sync/";,sync_view)、路径(";";,索引),]。

回到视图中,创建一个名为Smoke的新异步函数。此函数接受两个参数:一个名为Smokables的字符串列表和一个名为Essage的字符串。这些将分别默认为可烟熏肉类和甜美宝贝射线的列表。(#34;s";s;)。

#hello_async/views.py async def Smoke(Smokables:List[str]=None,For:Str=";Sweet Baby Ray";s&34;)->;None:";";";熏肉并涂抹Smoet Baby Ray。如果Smokables为None:Smokables=[";,";柠檬鸡";,";鲑鱼";,";野牛腰肉";,";香肠";,]if(Love_Smokable:=Smokables[0])==";肋骨";:Love_Smokable=";肉类";烟熏:打印(f";吸食一些{可吸烟})....。)等待异步信号。睡眠(1)打印(f";涂抹{风味}...";)等待异步。睡眠(1)打印(f";{Smokable.Capalize()}吸烟。";)打印(f";谁不喜欢吸烟{Love_Smokable}?";)。

如果未提供Smokables,则函数的第一行实例化默认肉类列表。然后,第二个语句将名为Love_Smokable的变量设置为Smokables中的第一个对象,只要第一个对象是肋骨。";For循环异步地将味道(Read:Sweet Baby Ray)应用到Smokables(Read:Smoked Meats)。

列表用于额外的键入功能。这不是必需的,很容易省略(只需将";Smokables";参数声明后面的:list[str]设为空)。

#hello_async/views.py async def Smoke_ome_meats(请求)->;HttpResponse:loop=asyncio。Get_event_loop()Smoke_args=[]if to_Smoke:=request。去拿吧。Get(";to_Smoke";):#抓起可吸烟的To_Smoke=to_Smoke。Split(";,";)Smoke_args+=[[Smokable.。较低()。如果(Smoke_List_len:=len(To_Smoke))==2:to_Smoke=";和";,则对TO_Smoke]]#中的Smokable执行一些字符串修饰。加入(To_Smoke)Elif Smoke_List_len>;2:To_Smoke[-1]=f";和{To_Smoke[-1]}";To_Smoke=";,";。加入(To_Smoke)Else:to_Smoke=";meats";if味道:=请求。去拿吧。Get(";味道";):Smoke_args。追加(风味)循环。CREATE_TASK(Smoke(*Smoke_Args))return HttpResponse(f";Smoking Some{to_Smoke}....";)。

该视图采用可选的查询参数TO_Smoke and For。To_Smoke是一个逗号分隔的要吸烟的东西的列表,而味道则是你要应用到它们上的东西。

这个视图做的第一件事(这在标准同步视图中是做不到的)是用asyncio.get_event_loop()获取事件循环。然后,它解析查询参数(如果适用)(并为最终的打印语句执行一些字符串清理)。如果我们没有传入任何东西来吸烟,则To_Smoke默认为肉类。最后,会返回一个响应,让用户知道他们正在准备美味的烧烤大餐。

太棒了。保存文件,然后返回您的浏览器并导航到http://localhost:8000/smoke_some_meats/.。您应该会得到这样的响应:

抽一些排骨...信息:127.0.0.1:56239-&34;GET/Smoke_Some_Meats/HTTP1.1&34;200OK使用甜蜜的婴儿射线...。肋骨冒烟了。抽一些胸脯肉..。涂抹甜蜜的婴儿射线。熏一些柠檬鸡。熏柠檬鸡肉。抽点三文鱼...。涂抹甜蜜的婴儿鱼片……三文鱼熏制。熏一些野牛腰肉……涂抹甜味的婴儿鱼片。野牛腰肉烟熏。抽一些香肠...。应用甜美的婴儿射线……香肠冒烟。谁不喜欢熏肉呢?

请注意,在记录200响应之前,肋骨是如何开始冒烟的。这就是工作中的异步性:当Smoke函数最初休眠一秒时,视图完成处理并返回响应。最终用户将在肉类开始冒烟时看到响应。

同样值得注意的是,如果您使用Django的开发服务器,您的服务器会很好地返回响应,但是任何异步操作都不会发生。以下是您的控制台日志的显示方式:

使用Uvicorn,我们还可以使用查询参数进行测试。试试http://localhost:8000/smoke_some_meats?to_smoke=ice奶油、香蕉、芝士&;口味=金邦德药粉。(空格将方便地自动转换)。

抽一些冰激凌...信息:127.0.0.1:56407-";Get/Smoke_Some_Meats/?To_Smoke=ic%20奶油,%20banana,%20cheese&;flavor=Gold%20Bond%20Medicated%20Powder Http/1.1and200OK应用金邦德药粉...冰激凌熏了一些香蕉...涂抹了金邦德药粉...香蕉熏了一些奶酪...涂抹了金邦德药粉...奶酪熏了。谁不抽;I don‘我不喜欢烟熏冰激凌?

如果从非异步视图调用非异步函数,将会发生同样的事情。

#hello_async/views.py def overSmoke()->;one:";";";如果没有干,它必须是未煮熟的";";";睡眠(5)打印(";谁不喜欢烤肉?";)。

#hello_async/urls.py from django.contrib import admin from django.urls从hello_async.views导入索引,async_view,sync_view,moke_ome_meats,burn_ome_meats urlpattern=[path(";admin/";,admin.。站点。URL)、路径(";Smoke_Some_Meats/";,Smoke_Some_Meats)、路径(";Burn_Some_Meats/";,Burn_Some_Meats)、路径(";异步/";,异步视图)、路径(";sync/";,sync_view)、路径(";";,index),]。

请注意,最终花了5秒钟才从浏览器获得响应。您还应该同时收到控制台输出:

可能值得注意的是,无论您使用的是WSGI服务器还是基于ASGI的服务器,都会发生同样的事情。

问:如果您在异步视图内进行同步和异步调用,该怎么办?

对于不同的目的,同步视图和异步视图往往工作得最好。如果你在异步视图中有阻塞功能,充其量也不会比仅仅使用同步视图更好。

如果需要在异步视图内进行同步调用(例如,通过Django ORM与数据库交互),可以使用sync_to_async作为包装器或装饰器。

#hello_async/views.py async def async_with_sync_view(Request):loop=asyncio。Get_event_loop()async_function=sync_to_async(Http_Call_Sync)循环。Create_task(async_function())return HttpResponse(";非阻塞HTTP请求(通过sync_to_async)";)

#hello_async/urls.py from django.contrib import admin from django.urls导入路径from hello_async.views import(index,async_view,sync_view,moke_ome_meats,burn_ome_meats,async_with_sync_view)urlpattern=[path(";admin/";,admin.。站点。URL)、路径(";Smoke_Some_Meats/";、Smoke_Some_Meats)、路径(";Burn_Some_Meats/";、Burn_Some_Meats)、路径(";sync_to_async/";、async_with_sync_view)、路径(";异步/&34;、异步视图)、路径(";sync/&。,sync_view),路径(";";,索引),]。

使用sync_to_async,阻塞的同步调用在后台线程中处理,从而允许在第一个休眠调用之前发回HTTP响应。

Django的异步视图提供了与任务或消息队列类似的功能,而没有复杂性。如果您正在使用(或正在考虑)Django,并且想要做一些简单的事情(例如给新订户发送电子邮件或调用外部API),异步视图是快速轻松地实现这一点的好方法。如果您需要执行更重的、长时间运行的后台进程,那么您仍然需要使用芹菜或RQ。

应该注意的是,要有效地使用异步视图,您应该在视图中只有异步调用。另一方面,任务队列使用独立进程上的工作进程,因此能够在多个服务器上的后台运行同步调用。

顺便说一句,您绝不能在异步视图和消息队列之间进行选择--您可以很容易地同时使用它们。例如:您可以使用异步视图发送电子邮件或一次性修改数据库,但是让芹菜在每晚的计划时间清理您的数据库,或者生成并发送客户报告。

对于新建项目,请利用异步视图,并以尽可能多的异步方式编写I/O进程。也就是说,如果您的大多数视图只需要调用数据库并在返回数据之前进行一些基本处理,您将不会看到与仅仅坚持使用同步视图相比有多大的增长(如果有的话)。

对于棕色项目,如果您只有很少的I/O进程或没有I/O进程,请坚持使用同步视图。如果您确实有许多I/O进程,请测量一下以异步方式重写它们有多容易。将同步I/O重写为异步并非易事,因此在尝试重写异步之前,您可能希望优化同步I/O和视图。此外,将同步过程与异步视图混合在一起从来都不是一个好主意。

在生产中,确保使用Gunicorn管理Uvicorn,以便同时利用并发性(通过Uvicorn)和并行性(通过Gunicorn工作进程):

总而言之,虽然这是一个简单的用例,但它应该会让您大致了解Django的新异步视图打开的可能性。在异步视图中可以尝试的其他操作包括发送电子邮件、调用第三方API和写入文件。想想代码中包含简单进程的那些视图,这些视图不一定需要直接向最终用户返回任何内容--这些视图可以快速转换为异步。

有关Django新发现的异步性的更多信息,请参阅这篇出色的帖子,它涵盖了相同的主题以及多线程和测试。