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.

La página de captura de fotogramas muestra cómo hacer que csi.CSI.snapshot() sea compatible con await; una vez que se tiene ese envoltorio, el cuerpo de un bloque async with Spotlight(led): sería normalmente un bucle de captura ejecutándose bajo la iluminación constante.