Table des matières

Python : programmation asynchrone

Présentation

Un programme est une succession d'instructions qui s'exécutent plus ou moins rapidement :

Ce mode de programmation est un appelé programmation synchrone.

Le mode programmation asynchrone permet alors de lancer l'exécution d'une instruction et, sans attendre la fin de l'exécution de celle-ci, de lancer l'exécution d'une autre instruction.

Cela est utile pour des instructions qui s'exécutent lentement comme la récupération ou l'écriture de données depuis le réseau ou sur le disque dur (les entrée/sorties) et que l'on souhaite na pas bloquer le programme tant que ces instructions ne sont pas terminées. Cela concerne donc particulièrement les applications du Web.

La programmation asynchrone permet de gérer un ensemble d’événements de façon concurrente.

Principe de la programmation asynchrone

L'idée générale de la programmation asynchrone est de pouvoir démarrer des opérations non bloquantes de façon concurrente, puis de pouvoir suivre leurs états et finalement exécuter des fonctions de retour (callbacks) quand elles sont prêtes.

Pour cela le programme va gérer une boucle qui attend que des événements (event loop) se produisent et alors fait correspondre à chaque événement qui survient la fonction qui lui est associée. C'est dans la fonction associée que sont définis les traitement à faire.

Exemple pour un serveur Web :

  • le serveur Web à un seul point d'entrée et dispose de plusieurs pages web ;
  • le serveur est en attente, c'est à dire à l'écoute des requêtes des clients (les navigateurs) qui peuvent demander telle ou telle page ;
  • dès qu'une requête arrive, le serveur Web retourne la page correspondant à la demande.

Précisions :

  • chaque requête des clients est considérée comme un événement distinct (event),
  • le serveur est programmé pour qu'à chaque événement une fonction prédéfinie soit exécutée. L'événement est le déclencheur de l'exécution de la fonction.

La bibliothèque asyncio

La bibliothèque asyncio permet de faire de la programmation asynchrone et dispose de deux éléments fondamentaux, une event loop et la classe Future.

La mise en oeuvre de la programmation asynchrone avec la bibliothèque asyncio nécessite l'utilisation deux mots clés async et await :

Première manière de gérer une boucle d'événement

Première possibilité pour créer une boucle d'événement (event loop) :

import asyncio
import time
 
# Definir une coroutine qui sera exécutée ultérieurement (dans le futur)
async def maCoroutine():
    print("Début de ma Coroutine")
    time.sleep(2)
    print("Fin de ma Coroutine")
 
# Faire tourner une simple boucle d'événement
# jusqu'à ce que l'exécution a la coroutine soit finie (completed)
loop = asyncio.get_event_loop()
loop.run_until_complete(maCoroutine())
 
# fermer la boucle d'événement
print("fermer la boucle d'événement")
loop.close()

Les options d'exécution

Pour exécuter la boucle d'événement on peut :

Autre alternative pour gérer une boucle d'événement avec la méthode run_forever()

La méthode run_forever() permet à la boucle d'événement de s'exécuter indéfiniment jusqu'à ce que le programme se termine ou si la méthode stop() est appelée. La méthode ensure_future() permet également de déclarer une coroutine et sera utile pour déclarer plusieurs coroutines dans la même boucle d'événement.

import asyncio
 
async def maCoroutine():
    while True:
        print("Début de ma Coroutine")
        await asyncio.sleep(1)
        print("Fin de ma Coroutine")
 
loop = asyncio.get_event_loop()
 
try:
    asyncio.ensure_future(maCoroutine())
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    # fermer la boucle d'événement
    print("fermer la boucle d'événement")
    loop.close()

Précisions :
La fonction utilise une boucle infinie et donc le programme va boucler indéfiniment. Il est possible alors :

  • d'interrompre le programme avec les touches d'interruption CTRL + C,
  • de gérer l'exception qui est générée,
  • pour afficher Fermer proprement la boucle et terminer proprement le programme.

Gérer des tâches en parallèle

Un intérête de la programmation asynchrone est de pourvoir gérer des tâches en parallèle :

import asyncio
 
async def tache_1():
    while True:
        print("Début de ma tâche 1")
        await asyncio.sleep(1)
        print("Fin de ma tâche 1")
 
async def tache_2():
    while True:
        print("Début de ma tâche 2")
        await asyncio.sleep(5)
        print("Fin de ma tâche 2")
 
loop = asyncio.get_event_loop()
 
try:
    asyncio.ensure_future(tache_1())
    asyncio.ensure_future(tache_2())
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    # fermer la boucle d'événement
    print("Fermer la boucle d'événement")
    loop.close()

Voici le résultat obtenu :

>>> 
 RESTART: D:/Developpement/python/Websocket/scripts/coroutine_future_parallele.py 
Début de ma tâche 1
Début de ma tâche 2
Fin de ma tâche 1
Début de ma tâche 1
Fin de ma tâche 1
Début de ma tâche 1
Fin de ma tâche 1
Début de ma tâche 1
Fin de ma tâche 1
Début de ma tâche 1
Fin de ma tâche 2
Début de ma tâche 2
Fin de ma tâche 1
Début de ma tâche 1
Fin de ma tâche 1
Début de ma tâche 1
Fermer la boucle d'événement
>>> 

La tâche 1 s'exécute plus souvent que la tâche 2. Ces deux tâches s'exécutent indépendamment l'une de l'autre.

Retour au cours : Les instructions du langage Python