2.32. Coroutines

Une coroutine est une fonction qui peut se mettre en pause en cours d’exécution puis reprendre plus tard là où elle s’était arrêtée, avec toutes ses variables locales intactes. Elle reflète le modèle des générateurs – un corps de fonction qui peut être suspendu et repris – avec une différence quant à qui pilote chaque reprise.

En Python, la syntaxe pour écrire une coroutine est la paire de mots-clés async / await. async marque la fonction comme étant une coroutine ; await marque les points internes où la mise en pause est autorisée.

2.32.1. Définir une coroutine

Une fonction définie avec async def est une fonction coroutine. L’appeler n’exécute pas le corps ; cela renvoie un objet coroutine qui n’a pas encore démarré :

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

L’objet coroutine est mis en pause tout au début de la fonction. Quelque chose doit le piloter pour que le corps s’exécute – et ce quelque chose est une boucle d’événements, un composant d’exécution. MicroPython en fournit une dans le module asyncio. Pour l’instant, considérez la coroutine comme « prête à s’exécuter, en attente d’un pilote ».

2.32.2. Mettre en pause au sein d’une coroutine

Au sein d’une coroutine, une expression await suspend l’exécution jusqu’à ce que la valeur attendue soit prête :

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

Lorsque le corps atteint await read_sensor(), la coroutine rend le contrôle à ce qui l’exécute. Quand read_sensor() se termine, le pilote reprend la coroutine à la ligne suivante, avec le résultat lié à data.

await n’est valide qu’au sein d’une coroutine. L’utiliser dans une fonction ordinaire est une erreur de syntaxe.

2.32.3. Relation avec les générateurs

Les coroutines et les générateurs partagent le même mécanisme sous-jacent. La distinction tient à qui déclenche chaque reprise :

  • Un générateur produit des valeurs ; le consommateur récupère la suivante avec next() ou par itération.

  • Une coroutine cède le contrôle ; une boucle d’événements planifie la reprise lorsque l’opération attendue est prête.

Si le mécanisme de cession (yield) des générateurs vous est clair, celui des coroutines repose sur la même idée – piloté par une boucle d’événements plutôt que par une boucle for.

Une boucle d’événements est un petit répartiteur qui tient une liste de coroutines en attente de quelque chose (un minuteur, un événement réseau, la fin d’une autre coroutine). À chaque itération, elle choisit une coroutine dont l’attente a été satisfaite, la reprend jusqu’au prochain await, puis enregistre ce que cette coroutine attend désormais et passe à une autre coroutine prête. Le résultat est de nombreuses tâches qui progressent simultanément sur un seul thread – chaque coroutine cède volontairement le contrôle à ses points await, et la boucle comble ces instants avec les autres coroutines prêtes à avancer.

En interne, await et yield utilisent la même fonctionnalité du runtime Python pour suspendre et reprendre une fonction. Les mots-clés diffèrent parce que la convention qui les entoure diffère : yield rend une valeur à un consommateur qui la récupère avec next() ; await cède le contrôle à une boucle d’événements qui planifie la reprise lorsque l’opération attendue est prête. async / await est essentiellement une syntaxe plus récente pour le modèle des coroutines – les anciennes bibliothèques construisaient les coroutines directement au-dessus de la machinerie des générateurs, en utilisant yield from (présenté dans Itérateurs et générateurs) pour déléguer la suspension entre coroutines.

2.32.4. Les coroutines ont besoin d’un pilote

Une coroutine est inerte sans un runtime pour la piloter. La définir est une chose ; l’exécuter requiert une boucle d’événements. Le module asyncio de MicroPython fournit cette boucle d’événements. La section Asyncio explique comment démarrer la boucle, y planifier des coroutines, partager l’état entre elles avec des verrous et des événements, gérer l’annulation et les délais d’attente, et structurer une application réelle autour des mots-clés async / await présentés ici.