TD : Programmation asynchrone avec asyncio
Créez un fichier par exercice, par exemple td4_ex1.py. Ne nommez jamais un fichier asyncio.py, random.py ou httpx.py : Python importerait votre fichier à la place du vrai module.
Exercice 1 : Première coroutine
Écrivez une coroutine dire_bonjour(nom, delai) qui affiche Bonjour {nom}, attend delai secondes avec await asyncio.sleep(delai), puis affiche Au revoir {nom}.
- Lancez-la avec
asyncio.run(...)pour"Alice"et un délai d’une seconde - Retirez le
awaitdevantasyncio.sleep(delai): que remarquez-vous ? Lisez bien le message affiché - Remettez le
await. Écrivez ensuite une coroutinemain()qui appelledire_bonjour("Alice", 1)sansawait, et lancezasyncio.run(main()): que se passe-t-il ?
Exercice 2 : Séquentiel contre concurrent
Reprenez la coroutine lire_capteur(nom, duree) du cours.
- Écrivez
main_sequentiel()qui lit trois capteurs avec troisawaitl’un après l’autre (T1 : 2 s, T2 : 1 s, T3 : 3 s) et affiche la durée totale avectime.perf_counter() - Écrivez
main_concurrent()qui fait la même chose avecasyncio.gather - Dans quel ordre les messages
terminés’affichent-ils ? Dans quel ordre sont les valeurs de la liste renvoyée pargather? Pourquoi ? - Complétez : la durée séquentielle est la __ des attentes, la durée concurrente est la __ des attentes
Exercice 3 : Un vrai appel HTTP
Jusqu’ici, les capteurs étaient simulés avec asyncio.sleep. Interrogeons maintenant une vraie API : Open-Meteo donne gratuitement la température actuelle d’un lieu, sans clé ni compte. Il suffit de lui envoyer les coordonnées GPS. Voici les quatre villes à interroger :
VILLES = [
("Marseille", 43.30, 5.37),
("Grenoble", 45.19, 5.72),
("Paris", 48.85, 2.35),
("Lille", 50.63, 3.06),
]
- Dans votre environnement virtuel, installez la bibliothèque
httpxavecpip - Testez un appel ordinaire (non asynchrone) :
import httpx
reponse = httpx.get(
"https://api.open-meteo.com/v1/forecast",
params={"latitude": 43.30, "longitude": 5.37, "current": "temperature_2m"},
)
print(reponse.json())
Dans le dictionnaire affiché, retrouvez la température actuelle. Quel est son chemin d’accès, de la forme ["..."]["..."] ?
- Écrivez une fonction ordinaire
temperature(latitude, longitude)qui renvoie la température, puismain_sequentiel()qui affiche celle des 4 villes l’une après l’autre, avec la durée totale (time.perf_counter()) - Écrivez la version asynchrone : une coroutine
temperature_async(client, latitude, longitude)qui utiliseawait client.get(...)à la place dehttpx.get(...), puismain_concurrent()avecasyncio.gather. Le début demain_concurrentvous est donné :
async def main_concurrent():
async with httpx.AsyncClient() as client:
...
async withouvre le client et le referme proprement à la fin du bloc, commewith open(...)pour un fichier.
- Comparez les deux durées, puis relancez chaque programme plusieurs fois. Les durées changent-elles d’une exécution à l’autre ? Pourquoi, alors qu’avec
asyncio.sleepelles étaient toujours identiques ?
Exercice 4 : Le piège de time.sleep
Dans lire_capteur, remplacez await asyncio.sleep(duree) par time.sleep(duree) et relancez main_concurrent().
- Quelle durée totale obtenez-vous ?
- Expliquez en deux phrases pourquoi
gatherne sert plus à rien - Remettez la bonne version
Exercice 5 : Collecte de capteurs avec logging
- Configurez
loggingen niveauINFOavec un format qui affiche l’heure, le niveau et le message - Écrivez une coroutine
lire_capteur(nom)qui attend un temps aléatoire entre 0,5 et 2 secondes (random.uniform(0.5, 2)), puis renvoie une température aléatoire entre 18 et 25 (round(random.uniform(18, 25), 1)). Elle enregistre la valeur aveclogger.info - Écrivez une fonction ordinaire
moyenne(valeurs) - Dans
main(), lisez 5 capteurs (T1àT5) avecgather, affichez la moyenne et la durée totale
Indice : construisez une liste de coroutines avec une boucle, puis passez-la à
gatheravec une étoile :await asyncio.gather(*taches). L’étoile*transforme la liste en arguments séparés.
- La durée totale est-elle proche de la somme des attentes ou de la plus longue ?
Exercice 6 : Capteur qui ne répond pas
Reprenez l’exercice 5. Le capteur T3 ne répond jamais : simulez-le avec une attente de 10 secondes.
- Écrivez
lire_capteur_securise(nom, duree, timeout=2)qui appellelire_capteuravecasyncio.wait_for. En cas deasyncio.TimeoutError, elle enregistre unlogger.warninget renvoieNone - Dans
main(), lisez les 5 capteurs (dontT3) avecgather - Calculez la moyenne en ignorant les valeurs
None, et affichezMoyenne sur N capteurs - Quelle serait la moyenne si
T3était compté comme0? Comparez avec la vraie moyenne - Quelle est la durée totale du programme ? Pourquoi n’est-elle pas de 10 secondes ?
Exercice 7 (bonus) : Collecte périodique
Écrivez un programme qui répète la collecte de l’exercice 6 trois fois, avec une pause d’une seconde entre deux cycles (await asyncio.sleep(1)).
- Affichez le numéro du cycle avec
logger.infoau début de chaque cycle - Lisez les heures dans les messages du journal : combien de temps dure un cycle ? Pourquoi ?
- Que se passerait-il si vous ajoutiez un sixième capteur qui met 20 secondes à répondre, avec
timeout=2?