8.12. Context manager asincroni¶
La Panoramica di Python ha introdotto i context manager – gli oggetti che i blocchi with pilotano, con __enter__ eseguito all’ingresso e __exit__ eseguito all’uscita, indipendentemente da come termina il blocco. I context manager asincroni sono la stessa idea trasferita in asyncio: __aenter__ e __aexit__ sono coroutine, quindi la configurazione o la pulizia possono awaitare qualcosa. Il blocco che li pilota è async with.
Ne abbiamo già usato uno. asyncio.Lock è un context manager asincrono; la pagina lock lo ha usato come async with bus_lock: .... Questa pagina riguarda come scriverne uno per una risorsa propria di un’applicazione.
8.12.1. Quando scriverne uno¶
Quando l’applicazione ha una risorsa che richiede configurazione e smantellamento accoppiati, e almeno uno di questi due lati deve awaitare qualcosa. Connessioni di rete, sensori che necessitano di un ritardo di stabilizzazione dopo la configurazione, qualsiasi cosa che blocchi qualcosa all’ingresso e lo sblocchi all’uscita. La forma sincrona ordinaria with non è applicabile perché i suoi __enter__ ed __exit__ non possono essere coroutine.
8.12.2. I due metodi¶
async def __aenter__(self)viene eseguito quando il blocco viene avviato. Qualunque cosa restituisca è ciò a cui viene associata l’opzionale clausolaas namediasync with. Restituireselfè la forma più comune, ma qualsiasi valore va bene.async def __aexit__(self, exc_type, exc, tb)viene eseguito quando si esce dal blocco.exc_typeèNonein caso di uscita normale; in caso di eccezione (o di annullamento) è la classe dell’eccezione, edexcè l’istanza. Restituire un valore vero indica a Python che l’eccezione è stata gestita e non deve propagarsi. RestituireNone(il caso consueto) lascia che l’eccezione continui a risalire la catena delle chiamate dopo l’esecuzione della pulizia.
8.12.3. Un esempio elaborato¶
Un wrapper a faretto che accende un LED per la durata del corpo del blocco, con un breve ritardo di stabilizzazione affinché l’illuminazione sia stabile prima che avvengano eventuali acquisizioni, e spegne nuovamente il LED all’uscita:
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())
Quando si entra nel blocco, viene eseguito __aenter__: il LED si accende, l”await di stabilizzazione di 50 ms cede il controllo al loop così che altre coroutine possano avanzare nel frattempo, e il corpo del blocco inizia una volta completata l’attesa. Quando il blocco termina – per un’uscita normale, per un’eccezione o per un annullamento – viene eseguito __aexit__ e il LED si spegne di nuovo. La pulizia viene eseguita in ogni caso; è questa la garanzia che async with fornisce.
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.