说完了asyncio事件循环是如何运行异步任务的,接下来back to basic,我们一起看看async和await两个原语具体代表了什么含义。
首先是async,async通常用来修饰一个函数,表示这个函数会返回一个协程。比如说:
1 | async def _coro_maker(i): |
对_coro_maker进行反编译,得到这样的结果:
1 | Disassembly of _coro_maker: |
可以看到,函数体内反编译的结果和普通def函数是一致的,唯一的不同是最开始加了GEN_START字节码。首先看GEN_START的实现。
1 | case TARGET(GEN_START): { |
GEN_START对于生成器、协程、async生成器都适用。对于async def函数其真实含义是调用send(None)以触发协程的开始。由于async def生成的协程没有yield值,因此会报StopIteration异常并给出协程的返回值。
总的来说,协程是一种特殊的generator,具备被await的效果。
那么接下来,该说await了。我们看一组代码示例:
1 | async def _coro_maker(i): |
反编译_test_await的结果,得到:
1 | Disassembly of _test_await: |
首先来看GET_AWAITABLE,其代码实现如下:
1 | case TARGET(GET_AWAITABLE): { |
GET_AWAITABLE做了这样几件事情:
- 通过
_PyCoro_GetAwaitableIter获取一个Awaitable对象的迭代器iter - 检查
iter是否合法,检查当前Awaitable对象是否已经被await了 - 将
iter置于栈顶
Awaitable的iter到底是什么东西?我们来看_PyCoro_GetAwaitableIter的实现:
1 | PyObject * |
其中有重要的几句代码:
if (PyCoro_CheckExact(o) || gen_is_coroutine(o)) return ogetter = ot->tp_as_async->am_awaitPyObject *res = (*getter)(o)
可以知晓,如果对象是协程的话会直接返回,不是协程的话看有无ot->tp_as_async->am_await接口支持。如果再追究的话,对于一般的生成器PyGen_Type,是没有这个接口的,所以是无法被await的。
GET_AWAITABLE之后,接下来是load了一个None,然后YIELD_FROM。YIELD_FROM实现如下:
1 | case TARGET(YIELD_FROM): { |
通过YIELD_FROM操作,实际上调用了PyIter_Send(coro, None, &retval)。我们来看PyIter_Send的实现:
1 | PySendResult |
针对AwaitableIter,实际调用了Py_TYPE(iter)->tp_as_async->am_send(iter, arg, result),对应的函数是这个:
1 | static PySendResult |
看到这里就很令人熟悉了,没错,gen_send_ex2(coro, None, result, 0, 0)就是coro.send(None)的逻辑
在gen_send_ex2中,函数体的返回值会正好赋到result上。再看YIELD_FROM里if (gen_status == PYGEN_RETURN)分支,最终的返回值就会放到栈顶。
当我们调用xx = await Awaitable时,我们也就能够把Awaitable的返回值赋给xx了。这样,await原语就实现了它的作用。