8.10. 可等待类

8.10.1. 什么是可等待对象

当协程写下 await x 时,语言会询问 x 如何等待它。任何知道如何回答这个问题的对象都是 可等待的。可等待对象有两种:

  • async def 函数返回的协程对象。每次调用 send_request() 都会产生一个这样的对象——即协程 对象,而非函数的结果。写下 await send_request() 才会真正运行其函数体。

  • 定义了 __await__ 方法的类的对象。async def 是创建第一种对象的简写;__await__ 是手动创建第二种对象的方式。

应用程序代码默认使用第一种。第二种存在是为了应对那种少见的情况:需要让 对象本身 成为调用者 await 的目标,而不是对它调用某个方法所得的结果。

8.10.2. 两种形式的对比

同样的逻辑分别写成协程和可等待类:

import asyncio

# async def -- almost always the right choice
async def yield_then_return(value):
    await asyncio.sleep_ms(0)
    return value

# awaitable class -- equivalent, rarely written
class YieldThenReturn:
    def __init__(self, value):
        self._value = value

    def __await__(self):
        yield                       # let the loop run once
        return self._value          # value of the ``await`` expression

两者都能以相同的方式被 await:

async def main():
    a = await yield_then_return(1)
    b = await YieldThenReturn(2)
    print(a, b)                     # prints: 1 2

async def 形式更简短,读起来就像普通的 Python,并让语言来管理所有挂起与恢复的记账工作。在这个示例中,类形式没有带来任何额外的好处。

8.10.3. 类形式何时显现价值

当对象本身就必须 应用程序传来传去的东西——而不是调用者为获取协程而调用的函数时——类形式是唯一的选择:

  • 一个 类似 future 的值,由应用程序创建,交给生产者,稍后再 await 它以取回所生产的结果。

  • 一个在现有原语之上构建的自定义原语,其自然的 API 是 await my_thing 而非 await my_thing.wait()asyncio.Task 类本身就是一个内置示例——之所以可以写 await task,是因为 Task 定义了 __await__

对于任何符合 调用一个函数、得到一个协程、await 它 这种形式的情况,async def 胜出。

8.10.4. 协议细节

__await__ 是一个普通方法(不是 async def),它返回一个迭代器。将它写成生成器——即在其函数体中至少包含一个 yield——会自动使它成为生成器。每个 yield 都会挂起 await 该对象的协程,并将控制权交还给事件循环;当循环下一次调度该任务时,生成器会恢复。一个裸露的 return(或执行到末尾)会结束等待;return 所产生的任何东西都会成为该 await 表达式的值。

在实践中,少数这样做的类会把产出工作交给某个现有的可等待对象,而不是自己驱动循环:

class WaitForEvent:
    def __init__(self, event):
        self._event = event

    def __await__(self):
        yield from self._event.wait().__await__()
        return self._event

yield from 会复用底层事件的 __await__ 来完成实际的挂起。大多数 asyncio 应用程序永远都不需要编写这两种形式中的任何一种。