Python – 等待异步函数的完成
本文演示了如何创建一个异步函数并使用await
关键字来中断一个进程。我们还将 “学习如何在Python中使用任务而不是线程。
使用await
关键字并制作一个异步函数
异步编程不是多线程,它不是多进程,而是并发编程。
我们不会谈论整个并发编程的概念和整个编码模式,但我们会谈论基本原则以及我们如何在Python中实现这些原则。
现在让我们看一个简单的例子;我们有一个Func_1
,Func_2
和Func_3
,它们正在被调用。
Func_1()
Func_2()
Func_3()
如果这些函数被异步调用,这意味着我们将调用Func_1()
,然后我们将调用Func_2()
。
当Func_1()
返回时,我们将只调用Func_2(),
,当Func_2()
返回时,我们将调用Func_3()
。
如果我们使用多线程或多进程,那就与异步编程不一样了。因为在多线程中,我们会在这种情况下定义三个线程,我们会同时运行所有这些函数。
或者说,大致上是同时,我们将尝试同时运行它们,或者至少创造出同时执行的假象。
但是,我们想做的是,比方说,Func_1()
做一些有成效的事情,然后它从数据库、从API请求一些数据,或者它只是为了等待而一般地睡眠。
如果发生这种情况,我们不想浪费CPU时间,开始执行Func_2()
,尽管这个函数还没有返回。所以我们只能同时运行一个任务;我们不做任何多进程或多线程。
但是,如果Func_1()
处于睡眠或等待状态,或处于非生产状态,所以我们可以利用这段时间开始执行Func_2()
,也许还有Func_3()
。要在Python中进行异步编程,我们必须导入一个叫做asyncio
的库。
由于我们不会将整个程序定义为异步,特定的函数将是异步的;我们需要使用async
关键字来指定一个异步函数。
如果我们只有这个Main_Func()
,整个程序将是异步的,但是我们将在下一个例子中增加其他的函数。在这个函数里面,我们将使用两个print()
函数。
而且,在这中间,我们要进行睡眠,但我们不打算用time.sleep()
;我们要用asyncio.sleep()
。
在调用asyncio.sleep()
之前,我们需要使用await
关键字,这意味着我们将等待第二个打印语句的完成。在完成之前,我们不打算做任何其他事情。
为了运行Main_Func()
函数,我们需要使用asyncio.run()
,在run()
函数里面我们将传递Main_Func()
函数。我们必须调用Main_Func()
函数;我们不仅仅是指多线程。
import asyncio
async def Main_Func():
print('Before waiting')
await asyncio.sleep(1)
print('After waiting')
asyncio.run(Main_Func())
输出:
让我们引入另一个异步函数,称为Func_2()
;我们将打印两个语句并睡眠两秒钟。
在Main_Func()
函数内部,我们不需要睡眠,而是用await
关键字调用Func_2()
函数,但这不会是异步的。
import asyncio
async def Main_Func():
print('Before waiting')
await Func_2()
print('After waiting')
async def Func_2():
print('Func_2: Before waiting')
await asyncio.sleep(2)
print('Func_2: After waiting')
asyncio.run(Main_Func())
因为我们在等待这个函数,这和直接异步调用它是一样的,它不会执行这个,直到所有的指令都按照我们的定义完成。
Before waiting
Func_2: Before waiting
Func_2: After waiting
After waiting
这不是异步的;但是,如果我们想在Main_Func()
函数调用时做这样的事情,那么控件应该打印Main_Func()
函数的第一个print
语句。
然后,调用Func_2()
函数,从而打印这个函数的第一个print
语句。
当这个函数处于睡眠状态时,它应该打印Main_Func()
函数的第二条print
语句,一旦完成,它应该打印Func_2()
函数的第二条print
语句。
使用create_task()
,创建任务来修复一个问题
要做到这一点,我们需要与tasks
,所以,在Main_Func()
函数的开头,我们要创建一个任务,同时调用asyncio.create_task()
,在任务里面,我们将传递Func_2()
。
这意味着,一旦我们有一些空闲时间,我们将调用该任务。
import asyncio
async def Main_Func():
Task=asyncio.create_task(Func_2())
print('Before waiting')
print('After waiting')
async def Func_2():
print('Func_2: Before waiting')
await asyncio.sleep(2)
print('Func_2: After waiting')
asyncio.run(Main_Func())
运行该代码后,我们可以看到两个打印语句从Main_Func()
函数中打印出来,然后就完成了。第一条print
语句被执行,但在打印第二条print
语句之前,执行被终止了。
Before waiting
After waiting
Func_2: Before waiting
这是因为Main_Func()
是主函数,控件没有等待Func_2()
函数,这意味着一旦控件到达Main_Func()
函数的末端,控件就停止执行。
控件不需要等待Func_2()
函数的第二个print
语句完成,这样控件就会跳过它。为了解决这个问题,我们将在Main_Func()
函数的最后使用await Task
。
import asyncio
async def Main_Func():
Task=asyncio.create_task(Func_2())
print('Before waiting')
print('After waiting')
await Task
async def Func_2():
print('Func_2: Before waiting')
await asyncio.sleep(2)
print('Func_2: After waiting')
asyncio.run(Main_Func())
现在我们可以看到,它是按照定义打印的。
Before waiting
After waiting
Func_2: Before waiting
Func_2: After waiting
如果我们想看看它是如何异步工作的,我们可以在print
语句之间使用sleep()
函数来进行。
这意味着我们将执行Main_Func()
的第一个print
语句,然后控件将睡眠一秒钟,这意味着主函数现在有空闲时间。
而现在,任务有时间被执行,直到Main_Func()
完全运行。由于Main_Func()
函数处于睡眠状态,意味着我们现在有CPU时间,可以通过使用任务调用Func_2()
函数开始执行。
但是,Func_2()
函数也进入了睡眠状态,这意味着在这个函数里面控制权等待了两秒钟,控制权进入了Main_Func()
函数,打印第二个print
语句。
然后,它就终止了,也就是说,控件对Func_2()
函数的其他部分不再感兴趣了。
async def Main_Func():
Task=asyncio.create_task(Func_2())
print('Before waiting')
await asyncio.sleep(1)
print('After waiting')
输出:
Before waiting
Func_2: Before waiting
After waiting
如果我们想对此感兴趣,我们必须在Main_Func()
函数的末尾使用里面的await Task
。
它是异步的;你可以看到,顺序是:Main_Func()
函数打印第一个print
语句,然后Func_2()
先打印,然后Main_Func()
打印第二个,以及Func_2()
打印第二个。
因为当函数进入睡眠状态时,会使用空闲时间,这样就不会浪费CPU时间了。