8.12. Gestionnaires de contexte asynchrones

L”aperçu de Python a présenté les gestionnaires de contexte – les objets que pilotent les blocs with, avec __enter__ qui s’exécute à l’entrée et __exit__ qui s’exécute à la sortie, quelle que soit la manière dont le bloc se termine. Les gestionnaires de contexte asynchrones sont la même idée transposée dans asyncio : __aenter__ et __aexit__ sont des coroutines, de sorte que la mise en place ou le nettoyage peut await quelque chose. Le bloc qui les pilote est async with.

Nous en avons déjà utilisé un. asyncio.Lock est un gestionnaire de contexte asynchrone ; la page sur les verrous l’a utilisé sous la forme async with bus_lock: .... Cette page traite de la manière d’en écrire un pour la propre ressource d’une application.

8.12.1. Quand en écrire un

Lorsque l’application a une ressource qui nécessite une mise en place et un démantèlement appariés, et qu’au moins l’un de ces deux côtés doit await quelque chose. Connexions réseau, capteurs nécessitant un délai de stabilisation après configuration, tout ce qui verrouille quelque chose à l’entrée et le déverrouille à la sortie. La forme synchrone ordinaire with ne s’applique pas, car ses __enter__ et __exit__ ne peuvent pas être des coroutines.

8.12.2. Les deux méthodes

  • async def __aenter__(self) s’exécute à l’entrée du bloc. Ce qu’il renvoie est ce à quoi la clause facultative as name de async with est liée. Renvoyer self est la forme la plus courante, mais n’importe quelle valeur convient.

  • async def __aexit__(self, exc_type, exc, tb) s’exécute à la sortie du bloc. exc_type vaut None lors d’une sortie normale ; en cas d’exception (ou d’annulation), il s’agit de la classe de l’exception, et exc est l’instance. Renvoyer une valeur vraie indique à Python que l’exception a été gérée et ne doit pas se propager. Renvoyer None (le cas habituel) laisse l’exception poursuivre sa remontée dans la chaîne d’appels après l’exécution du nettoyage.

8.12.3. Un exemple concret

Un wrapper de projecteur qui allume une LED pendant le corps du bloc, avec un court délai de stabilisation afin que l’éclairage soit stable avant que toute capture ne se produise, et éteint à nouveau la LED à la sortie

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

Lorsque le bloc est entré, __aenter__ s’exécute : la LED s’allume, l”await de stabilisation de 50 ms redonne la main à la boucle afin que d’autres coroutines puissent progresser pendant ce temps, et le corps du bloc démarre une fois l’attente terminée. Lorsque le bloc se termine – lors d’une sortie normale, d’une exception ou d’une annulation – __aexit__ s’exécute et la LED s’éteint de nouveau. Le nettoyage s’exécute dans tous les cas ; c’est la garantie qu’offre async with.

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.