8.12. Gestores de contexto asíncronos

La Introducción a Python presentó los gestores de contexto – los objetos que controlan los bloques with, con __enter__ ejecutándose a la entrada y __exit__ ejecutándose a la salida sin importar cómo termine el bloque. Los gestores de contexto asíncronos son la misma idea trasladada a asyncio: __aenter__ y __aexit__ son corrutinas, de modo que la configuración o la limpieza pueden hacer await de algo. El bloque que los controla es async with.

Ya hemos usado uno. asyncio.Lock es un gestor de contexto asíncrono; la página de locks lo usó como async with bus_lock: .... Esta página trata sobre escribir uno para el propio recurso de una aplicación.

8.12.1. Cuándo escribir uno

Cuando la aplicación tiene un recurso que necesita configuración y desmontaje emparejados, y al menos uno de esos lados tiene que hacer await de algo. Conexiones de red, sensores que necesitan un retardo de estabilización tras la configuración, cualquier cosa que bloquee algo a la entrada y lo desbloquee a la salida. La forma síncrona simple with no aplica porque sus __enter__ y __exit__ no pueden ser corrutinas.

8.12.2. Los dos métodos

  • async def __aenter__(self) se ejecuta cuando se entra en el bloque. Lo que retorna es aquello a lo que se vincula la cláusula opcional as name de async with. Retornar self es la forma más común, pero cualquier valor funciona.

  • async def __aexit__(self, exc_type, exc, tb) se ejecuta cuando se sale del bloque. exc_type es None en una salida normal; en una excepción (o una cancelación) es la clase de la excepción, y exc es la instancia. Retornar un valor verdadero le dice a Python que la excepción ha sido manejada y no debe propagarse. Retornar None (el caso habitual) deja que la excepción continúe hacia arriba por la cadena de llamadas después de que se ejecute la limpieza.

8.12.3. Un ejemplo trabajado

Un envoltorio de foco que enciende un LED durante el cuerpo del bloque, con un breve retardo de estabilización para que la iluminación sea estable antes de que ocurran las capturas, y vuelve a apagar el LED a la salida:

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

Cuando se entra en el bloque, se ejecuta __aenter__: el LED se enciende, el await de estabilización de 50 ms cede el control al bucle para que otras corrutinas puedan avanzar mientras tanto, y el cuerpo del bloque comienza una vez que la espera se completa. Cuando el bloque termina – en una salida normal, en una excepción o en una cancelación – se ejecuta __aexit__ y el LED se vuelve a apagar. La limpieza se ejecuta en todos los casos; esa es la garantía que async with proporciona.

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.