Python async, await 的理解与使用

关于 Python 中 async, await 关键字的一些知识在去年的一篇 探索 Flask 对 asyncio 的支持 有讲到,一直没在实际上用过它们,所以基本上也就忘干净了。随着 Flask 2 加入了 async 的特性,以及 FastAPI 从一开始支持 async, 又觉得有必要重新温习一下 Python 中如何使用 async, await 关键字了。

注:由于 Flask 支持了 async, 号称 async 化 Flask 的  Quart 项目开始变得无足轻重了。

本文主要的学习材料是在 YouTube 上的一个视频 Fear and Awaiting in Async (Screencast), 其中用 Python REPL 以 Live 的形式展示,对 async, await 关键字循序渐进的讲解。

如今不少语言都支持 async, await 关键字,如 C#, JavaScript, Kotlin, Rust 等,还有今天的主角 Python。而 Java 仍然很重视函数返回值的意义,未支持 async, 只能显式的返回 Future/CompletableFuture, 而且自己控制如何在线程池中执行。

这又来到了一个叫做纤程的概念,它隐藏在 Python 的 async, await 背后,Go 语言对纤程的支持就更简单了, go foo() 就是了。

async 关键字的函数

调用后,函数立即执行

对于一个普通的没有 async 关键字的函数,调用它直接得到它的返回值,如果给它加上 async 关键字会怎么样呢?

得到的是一个 coroutine 对象,函数没有立即执行。它有点像是其他语言的 Promise 或 Future,下一步需要对兑现它,在 Python 中要去兑现一个 coroutine 可调用它的 send() 方法

coroutine.send(None) 触发了它的执行,同时注意到函数抛出了一个异常 StopIteration(Exception), 该异常的 value 就是 async 函数的返回值,所以也就可以定义一个 run() 函数来执行 async 函数

这就要问,加没有 async 有什么区别呢?我们先从字节码来对比以下两个方法的不同

  1. def foo()
  2. async def foo()

唯一的不同是加了 async 关键字的函数第一条指令是 GEN_START,开始一个 Generator。

如果不想声明这个 run() 函数,也可以直接使用  asyncio 提供的  run 函数

asyncio.run() 实际是用 event_loop() 来执行 coroutine 的。

await, async 函数调用另一个 async 函数

明白了 async 函数返回的是一个  coroutine 对象后,这就能指导我们如何去调一个 async 函数,特别是由一个 async 函数调用另一个 async 函数

如果一个普通函数调用 async 函数,显然只会得到一个 coroutine 对象

并且 Python 解释器会发出警告说 coroutine was never awaited, 因为如果没有 await 的话,对 greeting('Blog') 的调用永远得不到执行,也就失去的调用的必要性。

如果由一个 async 函数按传统方式来调用另一个 async 函数会怎么样呢?

没什么意外,main() 返回一个 coroutine, 其他什么也没发生。如果试图去兑现 main() 调用,相当于是普通函数调用了一个  async 函数。

这时就引出 await 关键字了,当一个 async 函数中调用另一个 async 函数时,必须在调用前加上 await 关键字,除非你就想得到一个 coroutine  对象。

总结起来就是:由 async 函数发起的对其他 async 函数的调用时,都必须加上 await 关键时。这里 greeting() 也是一个 async 函数,如果它又调用其他的 async 函数,需应用同样的规则,相当于一个 await 链。当对入口的 async 函数发起执行(兑现)时,将会发现链式反应。

什么时候不能用 await 呢?

在 Python REPL 中对 async 函数不能用 await

普通函数对 async 函数的调用不能 await

上面那两种是一样的情况,因 Python REPL 就是用一个普通函数作为入口的

对一个普通函数用 await 语法上不报错,但执行会有问题

能不用用 await

  1. 针对 coroutine 使用 await, 也就是调用 async 修饰的函数用 await
  2. 发起调用的函数必须为一个 async 函数,或 coroutine.send(), 或者 asyncio 的 event loop
  3. 随着 Python 版本的演进,更多的地方可以用 await,只要跟着感觉走,哪里不能用 await 错了次就知道了

asyncio 调用 async 函数

 

链接:

  1. Fear and Awaiting in Async (Screencast)

类别: Python. 标签: , . 阅读(49). 订阅评论. TrackBack.
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x