8.12. 异步上下文管理器

Python 概览 介绍了上下文管理器——由 with 块驱动的对象,其中 __enter__ 在进入时运行,__exit__ 在退出时运行,无论该块如何结束。异步上下文管理器是同一个理念被移植到 asyncio 中:__aenter____aexit__协程,因此其设置或清理可以 await 某些东西。驱动它们的块是 async with

我们已经用过一个了。asyncio.Lock 就是一个异步上下文管理器; 页面就以 async with bus_lock: ... 的形式使用了它。本页讲的是为应用程序自己的资源编写一个异步上下文管理器。

8.12.1. 何时编写它

当应用程序拥有一个需要成对进行设置与拆除的资源,并且这两侧中至少有一侧必须 await 某些东西时。例如网络连接、配置后需要一段稳定延迟的传感器、任何在进入时锁定某物并在退出时解锁的东西。普通的同步 with 形式不适用,因为它的 __enter____exit__ 不能是协程。

8.12.2. 两个方法

  • async def __aenter__(self) 在进入块时运行。它返回的任何东西就是 async with 的可选 as name 子句所绑定的对象。返回 self 是最常见的形式,但任何值都可以。

  • async def __aexit__(self, exc_type, exc, tb) 在退出块时运行。在正常退出时 exc_typeNone;在发生异常(或取消)时它是异常的类,而 exc 是其实例。返回一个为真的值会告诉 Python 该异常已被处理、不应继续传播。返回 None(通常的情况)则让异常在清理运行之后继续沿调用链向上传播。

8.12.3. 一个实际示例

一个聚光灯封装器,它在块的主体期间点亮一个 LED,并带有一段短暂的稳定延迟,使照明在任何采集发生之前都保持稳定,然后在退出时再次熄灭该 LED:

import asyncio
from machine import LED


class Spotlight:
    def __init__(self, led):
        self._led = led

    async def __aenter__(self):
        self._led.on()
        await asyncio.sleep_ms(50)
        return self

    async def __aexit__(self, exc_type, exc, tb):
        self._led.off()

async def main():
    led = LED("LED_WHITE")

    async with Spotlight(led):
        # work that benefits from steady illumination
        await asyncio.sleep_ms(200)

asyncio.run(main())

当进入块时,__aenter__ 运行:LED 点亮,50 ms 的稳定 await 让出控制权给循环,以便其他协程在此期间能够推进,块主体则在该等待完成后开始。当块结束时——无论是正常退出、发生异常、还是被取消——__aexit__ 都会运行并使 LED 重新熄灭。清理在每种情况下都会运行;这正是 async with 所提供的保证。

The frame capture page shows how to make csi.CSI.snapshot() await-friendly; once that wrapper is in hand, the body of an async with Spotlight(led): block would typically be a capture loop running under the steady illumination.