8.12. Gestores de contexto assíncronos

A Visão Geral do Python apresentou os gestores de contexto – os objetos que os blocos with utilizam, com __enter__ a executar na entrada e __exit__ a executar na saída independentemente de como o bloco termina. Os gestores de contexto assíncronos são a mesma ideia aplicada ao asyncio: __aenter__ e __aexit__ são coroutines, pelo que a configuração ou a limpeza podem usar await. O bloco que os aciona é async with.

Já usámos um. asyncio.Lock é um gestor de contexto assíncrono; a página de locks utilizou-o como async with bus_lock: .... Esta página é sobre como escrever um para um recurso próprio da aplicação.

8.12.1. Quando escrever um

Quando a aplicação tem um recurso que precisa de configuração e limpeza emparelhadas, e pelo menos um desses lados tem de usar await. Ligações de rede, sensores que precisam de um atraso de estabilização após a configuração, qualquer coisa que bloqueie algo na entrada e desbloqueie na saída. A forma síncrona simples with não se aplica porque os seus __enter__ e __exit__ não podem ser coroutines.

8.12.2. Os dois métodos

  • async def __aenter__(self) executa quando o bloco é iniciado. O que quer que devolva é o que a cláusula opcional as name de async with fica a referenciar. Devolver self é a forma mais comum, mas qualquer valor funciona.

  • async def __aexit__(self, exc_type, exc, tb) executa quando o bloco termina. exc_type é None numa saída normal; numa exceção (ou num cancelamento) é a classe da exceção, e exc é a instância. Devolver um valor verdadeiro diz ao Python que a exceção foi tratada e não deve propagar-se. Devolver None (o caso habitual) deixa a exceção continuar a subir na cadeia de chamadas após a execução da limpeza.

8.12.3. Um exemplo prático

Um invólucro de holofote que liga um LED para o corpo do bloco, com um pequeno atraso de estabilização para que a iluminação esteja estável antes de qualquer captura, e desliga o LED novamente na saída:

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 o bloco é iniciado, __aenter__ executa: o LED acende-se, os 50 ms de estabilização com await cedem o controlo ao loop para que outras coroutines possam progredir entretanto, e o corpo do bloco começa assim que a espera termina. Quando o bloco termina – numa saída normal, numa exceção, ou num cancelamento – __aexit__ executa e o LED apaga-se novamente. A limpeza é executada em todos os casos; é essa a garantia que async with oferece.

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.