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.