8.12. Async context managers

Het Python-overzicht introduceerde context managers – de objecten die with-blokken aansturen, waarbij __enter__ draait bij het binnenkomen en __exit__ draait bij het verlaten, ongeacht hoe het blok eindigt. Async context managers zijn hetzelfde idee, verplaatst naar asyncio: __aenter__ en __aexit__ zijn coroutines, zodat de setup of de opschoning iets kan awaiten. Het blok dat ze aanstuurt is async with.

We hebben er al een gebruikt. asyncio.Lock is een async context manager; de pagina locks gebruikte het als async with bus_lock: .... Deze pagina gaat over het schrijven van er een voor een eigen resource van een applicatie.

8.12.1. Wanneer je er een moet schrijven

Wanneer de applicatie een resource heeft die gepaarde setup en teardown nodig heeft, en ten minste een van die kanten iets moet awaiten. Netwerkverbindingen, sensoren die een instabilisatievertraging nodig hebben na configuratie, alles wat iets vergrendelt bij binnenkomst en ontgrendelt bij vertrek. De gewone synchrone with-vorm is niet van toepassing omdat zijn __enter__ en __exit__ geen coroutines kunnen zijn.

8.12.2. De twee methoden

  • async def __aenter__(self) draait wanneer het blok wordt binnengekomen. Wat het ook retourneert, is waar de optionele as name-clausule van async with aan bindt. self retourneren is de meest voorkomende vorm, maar elke waarde werkt.

  • async def __aexit__(self, exc_type, exc, tb) draait wanneer het blok wordt verlaten. exc_type is None bij een normaal vertrek; bij een exceptie (of een annulering) is het de klasse van de exceptie, en exc is de instantie. Het retourneren van een waarheidsgetrouwe waarde vertelt Python dat de exceptie is afgehandeld en niet mag propageren. Het retourneren van None (het gebruikelijke geval) laat de exceptie verdergaan omhoog door de aanroepketen nadat de opschoning is gedraaid.

8.12.3. Een uitgewerkt voorbeeld

Een spotlight-wrapper die een LED aanzet voor de duur van het blok, met een korte instabilisatievertraging zodat de verlichting stabiel is voordat er beelden worden vastgelegd, en de LED weer uitzet bij het verlaten:

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

Wanneer het blok wordt binnengekomen, draait __aenter__: de LED gaat aan, de await van 50 ms instabilisatie geeft de controle terug aan de loop zodat andere coroutines ondertussen voortgang kunnen maken, en de body van het blok start zodra het wachten is voltooid. Wanneer het blok eindigt – bij een normaal vertrek, bij een exceptie of bij annulering – draait __aexit__ en gaat de LED weer uit. De opschoning draait in elk geval; dat is de garantie die async with biedt.

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.