Python – 等待异步函数的完成

本文演示了如何创建一个异步函数并使用await 关键字来中断一个进程。我们还将 “学习如何在Python中使用任务而不是线程。

使用await 关键字并制作一个异步函数

异步编程不是多线程,它不是多进程,而是并发编程。

我们不会谈论整个并发编程的概念和整个编码模式,但我们会谈论基本原则以及我们如何在Python中实现这些原则。

现在让我们看一个简单的例子;我们有一个Func_1Func_2Func_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())

输出:

Python – 等待异步函数的完成

让我们引入另一个异步函数,称为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时间了。

Python – 等待异步函数的完成