8.12. Asynchroniczne menedżery kontekstu

Przegląd Pythona wprowadził menedżery kontekstu – obiekty sterowane przez bloki with, w których __enter__ wykonuje się podczas wejścia, a __exit__ podczas wyjścia, niezależnie od tego, jak blok się kończy. Asynchroniczne menedżery kontekstu to ta sama idea przeniesiona do asyncio: __aenter__ i __aexit__korutynami, więc konfiguracja lub sprzątanie może coś await-ować. Blokiem, który nimi steruje, jest async with.

Już jednego użyliśmy. asyncio.Lock jest asynchronicznym menedżerem kontekstu; strona blokady używała go jako async with bus_lock: .... Ta strona dotyczy napisania własnego menedżera dla zasobu aplikacji.

8.12.1. Kiedy go napisać

Gdy aplikacja ma zasób wymagający sparowanej konfiguracji i likwidacji, a co najmniej jedna z tych stron musi coś await-ować. Połączenia sieciowe, sensory wymagające opóźnienia na ustabilizowanie po konfiguracji, wszystko, co blokuje coś przy wejściu i odblokowuje przy wyjściu. Zwykła synchroniczna postać with nie ma tu zastosowania, ponieważ jej __enter__ i __exit__ nie mogą być korutynami.

8.12.2. Dwie metody

  • async def __aenter__(self) wykonuje się przy wejściu do bloku. To, co zwraca, jest tym, do czego dowiązuje się opcjonalna klauzula as name w async with. Zwracanie self jest najczęstszą postacią, ale dowolna wartość działa.

  • async def __aexit__(self, exc_type, exc, tb) wykonuje się przy wyjściu z bloku. exc_type jest równe None przy normalnym wyjściu; przy wyjątku (lub anulowaniu) jest klasą wyjątku, a exc jest jego instancją. Zwrócenie wartości prawdziwościowej mówi Pythonowi, że wyjątek został obsłużony i nie powinien się propagować. Zwrócenie None (zwykły przypadek) pozwala wyjątkowi kontynuować w górę łańcucha wywołań po wykonaniu sprzątania.

8.12.3. Przykład w praktyce

Wrapper reflektora, który włącza diodę LED na czas trwania bloku, z krótkim opóźnieniem na ustabilizowanie, aby oświetlenie było stabilne przed jakimkolwiek przechwytywaniem, i ponownie wyłącza diodę LED przy wyjściu:

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())

Gdy blok jest wejściowy, wykonuje się __aenter__: dioda LED się zapala, await na 50 ms ustabilizowanie oddaje sterowanie pętli, aby inne korutyny mogły w międzyczasie robić postępy, a ciało bloku rozpoczyna się po zakończeniu oczekiwania. Gdy blok się kończy – przy normalnym wyjściu, przy wyjątku lub przy anulowaniu – wykonuje się __aexit__ i dioda LED ponownie gaśnie. Sprzątanie wykonuje się w każdym przypadku; to jest gwarancja zapewniana przez 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.