asyncio en Python : la bibliothèque qui orchestre les tâches asynchrones

asyncio occupe le temps mort d'un programme Python : pendant qu'une opération attend le réseau, la boucle d'événements en fait avancer une autre.
5 min de lecture
Believemy logo

Définition

Un programme qui va chercher cinquante pages sur Internet passe l'essentiel de son temps à attendre. Il envoie une demande, attend la réponse, recommence, et pendant ce temps le processeur ne fait rien.

asyncio est le module de la bibliothèque standard qui récupère ce temps mort. Il fournit la boucle d'événements, ce chef d'orchestre qui décide quelle coroutine avance et laquelle attend, ainsi que les outils pour les lancer de front.

Les mots-clés async et await ne font qu'inscrire une intention dans le code ; sans asyncio pour l'exécuter, ils ne produisent rien. La démonstration tient en quelques lignes : deux salutations lancées ensemble.

PYTHON
import asyncio

async def saluer(nom, delai):
    await asyncio.sleep(delai)   # laisse la boucle s'occuper des autres
    print("Bonjour", nom)

async def main():
    await asyncio.gather(
        saluer("Alice", 2),
        saluer("Bob", 1),
    )

asyncio.run(main())              # ouvre la boucle, la referme à la fin

Ce programme se termine en deux secondes et non en trois : pendant que la première salutation patiente, la seconde progresse. La durée totale n'est plus la somme des attentes mais la plus longue d'entre elles.


La boucle d'événements

La boucle est un guichet unique. Elle fait avancer une seule tâche à la fois et reprend la main dès que celle-ci rencontre une attente : elle passe à la suivante, et y reviendra quand la réponse arrivera.

Il n'y a donc qu'un seul fil d'exécution, jamais deux lignes au même instant. Cela a un bon côté : aucune instruction n'est interrompue en cours de route, donc pas de verrous à poser sur les variables partagées. La concurrence naît des seuls moments où une fonction rend la main volontairement, à chaque await.

Un seul appel ouvre et referme cette boucle, asyncio.run(), et tout le reste vit à l'intérieur. D'où une erreur classique : un await écrit au niveau du fichier lève une SyntaxError, faute de boucle pour l'accueillir.

Le mot async ne s'arrête pas aux fonctions : posé devant un with ou devant une boucle for, il donne un gestionnaire de contexte et un itérateur capables d'attendre à leur tour.


Lancer plusieurs choses de front

Premier essai, première déception : écrire deux await à la suite ne fait rien gagner. Le mot veut dire « attendre ici », donc le second appel ne démarre qu'une fois le premier fini.

Le chevauchement se demande explicitement. Voici les écritures qui servent au quotidien, et ce que chacune change.

ÉcritureCe qu'elle produit
await a() puis await b()Deux attentes bout à bout, aucun gain
asyncio.gather(a(), b())Les deux avancent ensemble, résultats dans une liste
asyncio.create_task(a())Le travail part en fond, il sera attendu plus tard
asyncio.wait_for(a(), 5)Une limite de temps, et une exception au-delà
asyncio.TaskGroup()Un groupe qui annule tout dès qu'un membre échoue

Le TaskGroup, arrivé avec Python 3.11, est la ligne à retenir : il garantit qu'aucune tâche ne survit à la sortie du bloc, alors qu'une tâche créée à la main puis jamais attendue disparaît en silence à la fermeture de la boucle.

Bon à savoir

asyncio ne rend pas asynchrone une bibliothèque qui ne l'est pas : il faut un client prévu pour, comme httpx ou aiohttp à la place de requests.


Le piège de l'appel bloquant

Tout repose sur une discipline : chaque tâche doit rendre la main régulièrement. Une ligne synchrone qui prend son temps gèle la boucle entière, et toutes les autres tâches font la queue derrière elle.

Les deux lignes qui suivent se ressemblent et ne font pas du tout la même chose.

PYTHON
import time

async def mesurer():
    time.sleep(3)           # met en pause le fil entier, boucle comprise
    await asyncio.sleep(3)  # rend la main : les autres tâches avancent

La première met le programme en sommeil, boucle comprise ; la seconde prévient la boucle qu'elle peut aller voir ailleurs. Le même écart vaut pour une requête réseau synchrone, la lecture d'un gros fichier ou un calcul lourd, avec asyncio.to_thread() pour sortie de secours.

Attention

Un appel bloquant ne lève aucune erreur et n'écrit rien dans les journaux. Le programme rend les bons résultats, simplement aussi lentement qu'une version synchrone : la cause peut coûter des heures à trouver.

Reste le choix de fond. asyncio excelle sur les attentes réseau, là où le programme ne fait que patienter des centaines de fois, comme un serveur web bâti sur FastAPI. Pour du calcul pur il n'apporte rien, faute d'attente à récupérer, et threading ou les processus séparés restent les bonnes réponses.


Questions fréquentes

Question

asyncio rend-il un programme plus rapide ?

Seulement s'il passe son temps à attendre : appels réseau, requêtes en base, lectures distantes. Le gain est proportionnel au temps mort que la boucle peut réutiliser. Sur un calcul qui occupe le processeur, le programme tourne à la même vitesse, avec du code plus difficile à relire.

Question

Pourquoi mes tâches s'arrêtent-elles avant la fin ?

Parce que asyncio.run() ferme la boucle dès que la coroutine principale se termine, sans attendre celles qui tournent encore. Une tâche lancée avec create_task n'est pas une promesse d'aller au bout : elle doit être attendue par un await, ou rangée dans un TaskGroup.

Question

Comment rattraper une erreur levée dans une tâche ?

Avec un try autour du await, et non autour de la ligne qui lance la tâche : l'erreur ne remonte qu'au moment où le résultat est réclamé. Sur un gather, l'option return_exceptions=True range les erreurs parmi les résultats au lieu d'interrompre le groupe. La formation Python consacre un chapitre à ces mécaniques d'attente.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.