Passer de la théorie à asyncio
Comprendre async et await est une première étape. Mais dans un vrai programme, il faut organiser plusieurs tâches, limiter les attentes, gérer les erreurs et arrêter proprement ce qui doit l’être. C’est le rôle du module standard asyncio.
Le réflexe important est de ne pas confondre concurrence et désordre. L’asynchrone permet de lancer plusieurs opérations, mais il faut garder une stratégie claire : quelles tâches sont attendues, quelles erreurs doivent arrêter le traitement, combien d’opérations peuvent tourner en même temps et que faire si une API ne répond pas.
Pour remettre ce sujet dans le parcours Python du site, vous pouvez aussi relire le langage Python, apprendre à coder en Python et les conseils pour écrire un code Python propre et lisible.
Lancer plusieurs tâches avec create_task
asyncio.create_task() planifie une coroutine dans la boucle courante. Cela permet de démarrer plusieurs opérations avant d’attendre leurs résultats.
import asyncio
async def traiter(item):
await asyncio.sleep(1)
return item * 2
async def main():
t1 = asyncio.create_task(traiter(10))
t2 = asyncio.create_task(traiter(20))
resultat1 = await t1
resultat2 = await t2
print(resultat1, resultat2)
asyncio.run(main())
Si vous faites simplement await traiter(10) puis await traiter(20), les opérations restent séquentielles. Avec des tâches, elles peuvent avancer pendant les temps d’attente.
gather pour agréger des résultats
Quand on veut lancer une liste de coroutines et récupérer les résultats dans l’ordre, asyncio.gather() est pratique.
async def main():
resultats = await asyncio.gather(*(traiter(i) for i in range(10)))
print(resultats)
Attention : si une coroutine échoue, gather propage l’exception. C’est souvent souhaitable, mais il faut le savoir. Pour récupérer les exceptions comme valeurs, on peut utiliser return_exceptions=True, puis traiter explicitement les échecs.
TaskGroup : une structure plus sûre
Depuis Python 3.11, asyncio.TaskGroup offre une approche structurée. Les tâches créées dans le groupe sont supervisées ensemble. Si l’une échoue, les autres sont annulées et l’erreur est remontée proprement.
async def main():
async with asyncio.TaskGroup() as tg:
for i in range(5):
tg.create_task(traiter(i))
Pour du code applicatif maintenable, cette logique est souvent préférable aux tâches “fire and forget” lancées sans suivi.
Ajouter des timeouts
Une application asynchrone sans timeout finit tôt ou tard par accumuler des tâches coincées. Toute opération réseau devrait avoir une limite claire.
async def main():
try:
async with asyncio.timeout(3):
await traiter(42)
except TimeoutError:
print("traitement trop long")
Le timeout n’est pas seulement une protection technique. C’est aussi une décision métier : combien de temps accepte-t-on d’attendre avant de passer à autre chose, d’afficher une erreur ou de réessayer ?
Annuler proprement une tâche
L’annulation est normale en asynchrone : un utilisateur ferme une connexion, un job dépasse son délai, une application s’arrête. Une coroutine doit donc libérer ses ressources dans un finally.
async def worker():
try:
while True:
await asyncio.sleep(1)
print("travail...")
finally:
print("nettoyage")
async def main():
task = asyncio.create_task(worker())
await asyncio.sleep(3)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("worker annulé")
Limiter la concurrence avec Semaphore
Lancer 1 000 appels HTTP en même temps peut saturer votre machine ou déclencher des limitations côté serveur. asyncio.Semaphore permet d’imposer un plafond.
sem = asyncio.Semaphore(10)
async def operation_limitee(item):
async with sem:
return await traiter(item)
Ce pattern est essentiel pour les scrapers, les appels API, les tâches de publication ou les pipelines de données.
Conclusion
Un bon programme asyncio n’est pas seulement un programme qui utilise await. C’est un programme qui encadre ses tâches : lancement explicite, attente contrôlée, timeout, annulation et limite de concurrence. Ces bases rendent ensuite les cas concrets beaucoup plus simples, notamment les appels HTTP asynchrones.
