1. Développement, langage

asyncio en pratique : tâches concurrentes, timeouts et annulation propre

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.

Frédéric Gaurat - formateur et développeur
Résumé de la politique de confidentialité

Ce site utilise des cookies afin que nous puissions vous fournir la meilleure expérience utilisateur possible. Les informations sur les cookies sont stockées dans votre navigateur et remplissent des fonctions telles que vous reconnaître lorsque vous revenez sur notre site Web et aider notre équipe à comprendre les sections du site que vous trouvez les plus intéressantes et utiles.