Module asyncio de Python
asyncioest un module de la bibliothèque standard Python, utilisé pour écrire du code d'opérations d'E/S asynchrones.
asyncio offre un moyen efficace de gérer des tâches concurrentes, particulièrement adapté aux opérations intensives en E/S telles que les requêtes réseau, la lecture et l'écriture de fichiers, etc.
En utilisantasyncio, vous pouvez traiter plusieurs tâches simultanément dans un seul thread sans utiliser plusieurs threads ou processus.
Pourquoi avons-nous besoin d'asyncio ?
Dans la programmation synchrone traditionnelle, lorsqu'une tâche doit attendre la fin d'une opération d'E/S (comme une requête réseau), le programme se bloque jusqu'à ce que l'opération soit terminée. Cela conduit à une faible efficacité, en particulier lorsqu'il s'agit de traiter un grand nombre d'opérations d'E/S.
asyncioEn introduisant le modèle de programmation asynchrone, le programme peut continuer à exécuter d'autres tâches pendant l'attente des opérations d'E/S, ce qui améliore la concurrence et l'efficacité du programme.
Imaginez que vous gérez un restaurant :
- Mode synchrone (fonctions ordinaires) :Vous n'avez qu'un seul chef. Le client A commande un steak, et le chef commence à faire frire le steak (cela nécessite d'attendre 5 minutes). Pendant ces 5 minutes de cuisson du steak, le chef est complètement occupé et ne peut rien faire d'autre. Même si le client B veut seulement un verre d'eau, il doit attendre.
- Mode asynchrone (asyncio) :Vous avez plusieurs chefs (en réalité toujours un seul, mais très intelligent). Après que le chef a commencé à faire frire le steak du client A, il se rend compte qu'il doit attendre. Il marque immédiatement le steak comme en attente, puis se retourne pour verser de l'eau au client B. Après avoir versé l'eau, il revient vérifier si le steak est presque prêt. S'il ne l'est pas encore, il peut aller traiter la commande du client C. De cette façon, pendant le temps d'attente des E/S (comme faire frire un steak, les requêtes réseau, la lecture et l'écriture de fichiers), le chef (CPU) travaille toujours efficacement.
asyncio est la bibliothèque standard que Python utilise pour mettre en œuvre ce mode de travail intelligent. Elle vous permet d'écrire du code concurrent mono-thread, particulièrement adapté aux scénarios intensifs en E/S tels que les robots d'exploration web, les serveurs web, les microservices, etc.
Son cœur est la boucle d'événements, les coroutines et les tâches.
Concepts fondamentaux d'asyncio
1. Coroutine
Une coroutine estasynciol'un des concepts fondamentaux. C'est une fonction spéciale qui peut se mettre en pause pendant l'exécution et reprendre plus tard. Les coroutines sont définies à l'aide duasync defmot-clé et, à travers leawaitmot-clé, met en pause l'exécution pour attendre qu'une opération asynchrone se termine.
Exemple
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
2. Boucle d'événements
La boucle d'événements estasynciole composant central, responsable de la planification et de l'exécution des coroutines. Elle vérifie continuellement s'il y a des tâches à exécuter et appelle les fonctions de rappel correspondantes une fois les tâches terminées.
Exemple
await say_hello()
asyncio.run(main())
3. Tâche
Une tâche est une enveloppe autour d'une coroutine, représentant une coroutine en cours d'exécution ou qui sera exécutée. Vous pouvez utiliser laasyncio.create_task()fonction pour créer une tâche et l'ajouter à la boucle d'événements.
Exemple
task = asyncio.create_task(say_hello())
await task
4. Future
Futureest un objet représentant le résultat d’une opération asynchrone. Il est généralement utilisé dans les API de bas niveau pour représenter une opération qui n’est pas encore terminée. Vous pouvez utiliser leawaitmot-clé pour attendreFuturepour terminer.
Exemple
future = asyncio.Future()
await future
Utilisation de base et exemples de code
Comprenons les concepts ci-dessus à travers un exemple classique d’accès simultané à plusieurs URL.
Supposons que nous devions récupérer le contenu de trois URL différentes. Avec une approche synchrone, les tâches s’exécutent séquentiellement et le temps total est la somme des trois temps de requête. Avecasyncio, nous pouvons envoyer ces trois requêtes en même temps, et le temps total est proche de celui de la requête la plus lente.
Version synchrone (pour comparaison)
Exemple
import requests
def fetch_url(url):
"""Simuler une requête réseau chronophage (version synchrone)"""
print(f"Début de récupération : {url}")
time.sleep(2) # Simuler 2 secondes de délai réseau
print(f"Récupération terminée : {url}")
return f"Données de {url}"
def main_sync():
urls = ['https://example.com/1', 'https://example.com/2', 'https://example.com/3']
results = []
start = time.time()
for url in urls:
result = fetch_url(url) # Doit attendre la fin du précédent avant de commencer le suivant
results.append(result)
end = time.time()
print(f"Temps total de la version synchrone : {end - start:.2f} secondes")
print(f"Résultats : {results}")
if __name__ == "__main__":
main_sync()
Sortie attendue:
开始获取: https://example.com/1 完成获取: https://example.com/1 开始获取: https://example.com/2 完成获取: https://example.com/2 开始获取: https://example.com/3 完成获取: https://example.com/3 同步版本总耗时: 6.00 秒 结果: [‘来自 https://example.com/1 的数据‘, ‘来自 https://example.com/2 的数据‘, ‘来自 https://example.com/2 的数据‘]
Cela prend environ 6 secondes au total.
Version asynchrone (avec asyncio)
Nous devons utiliseraiohttpla bibliothèque pour remplacerrequestspour effectuer des requêtes HTTP asynchrones. Tout d'abord, installez-la :pip install aiohttp。
Exemple
import aiohttp
import time
async def fetch_url_async(session, url):
"""Simuler une requête réseau chronophage (version asynchrone)"""
print(f"Début de la récupération asynchrone : {url}")
# Remarque : Ici, nous utilisons la méthode get asynchrone d'aiohttp et attendons avec await
async with session.get(url) as response:
# Simuler que le traitement de la réponse prend aussi du temps
await asyncio.sleep(2) # Utiliser asyncio.sleep pour simuler l'attente d'E/S ; cela ne bloque pas le thread
text = await response.text()
print(f"Récupération asynchrone terminée : {url}")
return f"Données de {url} (longueur : {len(text)})"
async def main_async():
urls = ['https://httpbin.org/get', 'https://httpbin.org/delay/1', 'https://httpbin.org/headers']
async with aiohttp.ClientSession() as session: # Créer une session HTTP asynchrone
# Créer une tâche (Task) pour chaque URL
tasks = []
for url in urls:
# create_task ajoute la coroutine à la boucle d'événements et démarre immédiatement la planification
task = asyncio.create_task(fetch_url_async(session, url))
tasks.append(task)
print("Toutes les tâches sont créées, démarrage de l'exécution concurrente...")
# Utilisez asyncio.gather pour exécuter toutes les tâches simultanément et attendre qu'elles se terminent toutes
# gather renvoie une liste de résultats, l'ordre correspondant à celui des tâches passées en entrée
results = await asyncio.gather(*tasks)
return results
if __name__ == "__main__":
start = time.time()
# asyncio.run() est un moyen pratique de démarrer la boucle d'événements et d'exécuter la coroutine de premier niveau
final_results = asyncio.run(main_async())
end = time.time()
print(f"\n"Durée totale de la version asynchrone : {end - start:.2f} secondes")
for res in final_results:
print(res)
Sortie attendue:
所有任务已创建,开始并发执行... 开始异步获取: https://httpbin.org/get 开始异步获取: https://httpbin.org/delay/1 开始异步获取: https://httpbin.org/headers (大约 2 秒后,所有请求几乎同时完成) 完成异步获取: https://httpbin.org/headers 完成异步获取: https://httpbin.org/get 完成异步获取: https://httpbin.org/delay/1 异步版本总耗时: 2.10 秒 # 注意!总耗时远小于 6 秒 来自 https://httpbin.org/get 的数据 (长度: 274) 来自 https://httpbin.org/delay/1 的数据 (长度: 392) 来自 https://httpbin.org/headers 的数据 (长度: 177)
Analyse de code:
async def: définit la fonction coroutinefetch_url_asyncetmain_async。await: dansfetch_url_async, nousawait session.get()etawait response.text(), cela indique à la boucle d'événements : « Cette requête réseau prend du temps ; va d'abord exécuter d'autres tâches prêtes. »asyncio.create_task(): enveloppe lafetch_url_asynccoroutine en uneTask, ce qui lui permet d'être planifiée par la boucle d'événements et d'atteindre la concurrence.asyncio.gather(*tasks): une fonction très pratique qui exécute en concurrence toutes les coroutines/tâches passées, attend qu'elles soient toutes terminées, puis collecte tous les résultats.asyncio.run(main_async()): l'approche recommandée à partir de Python 3.7+, responsable de la création de la boucle d'événements, de l'exécution de la coroutine et de la fermeture de la boucle.
Fonctions clés et descriptions des paramètres
Le tableau suivant répertorieasyncioplusieurs des fonctions de haut niveau les plus couramment utilisées :
| Fonction | Objectif principal | Descriptions des paramètres courants |
|---|---|---|
asyncio.run(coro, *, debug=False) |
Exécute une coroutine de niveau supérieur et gère le cycle de vie de la boucle d'événements. C'est le point d'entrée principal du programme. | coro: l'objet coroutine à exécuter.debug: défini àTruepour activer le mode débogage de la boucle d'événements. |
asyncio.create_task(coro, *, name=None) |
Enveloppe une coroutine dans un objetTasket la met en file d'attente dans la boucle d'événements pour planification. C'est le principal moyen d'obtenir de la concurrence. | coro: l'objet coroutine à envelopper.name: (Python 3.8+) spécifiez un nom pour la tâche afin de faciliter le débogage. |
asyncio.gather(*aws, return_exceptions=False) |
Exécute en parallèleplusieurs tâches asynchrones (awspeut accepter des coroutines, des tâches, etc.), attend que tout soit terminé et renvoie une liste de résultats. |
*aws: arguments variables, passez plusieurs objets asynchrones.return_exceptions: la valeur par défaut estFalse, toute exception levée par une tâche sera immédiatement propagée àgatherl'appelant. Défini surTruelorsque cette option est définie, les exceptions seront renvoyées dans les résultats. |
asyncio.sleep(delay, result=None) |
De manière asynchrone,suspend l'exécution pendant le nombre de secondes spécifié. C'est la principale différence avectime.sleep(bloquant). |
delay: Nombre de secondes de sommeil.result: La valeur renvoyée après la fin du sommeil. |
asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED) |
Exécute des tâches simultanément et attend que la condition spécifiée soit remplie. Renvoie deux ensembles(done, pending), qui sont respectivement les tâches terminées et non terminées. |
aws: Collection d’objets asynchrones.timeout: Durée du délai d’attente (secondes).return_when: Détermine quand renvoyer ; options :FIRST_COMPLETED(première terminée),FIRST_EXCEPTION(première exception),ALL_COMPLETED(toutes terminées, par défaut). |
asyncio.to_thread(func, /, *args, **kwargs) |
(Python 3.9+) Exécute une fonction synchrone ordinaire, potentiellement bloquante, dans un thread séparé et renvoie unawaitcoroutine awaitable. Utilisée pour gérer les tâches intensives en CPU ou les E/S bloquantes. |
func: La fonction synchrone à exécuter dans le thread.*args, **kwargs: Arguments passés à la fonction. |
Compréhension visuelle : Flux d'ordonnancement des tâches asynchrones

Explication du diagramme:
Ce diagramme montre comment la boucle d'événements fonctionne comme un répartiteur. Elle maintient une file de tâches. Lorsqu'une tâche s'exécute jusqu'àawait(par exemple, en attente d'une réponse réseau), elle est suspendue. La boucle d'événements trouve immédiatement la prochaine tâche exécutable (prête) dans la file et l'exécute. Lorsque l'opération d'E/S de la tâche suspendue se termine, la boucle d'événements reçoit une notification, remet le statut de la tâche à prêt et continue de l'exécuter à un moment futur. De cette façon, pendant les attentes d'E/S, le CPU est pleinement utilisé pour exécuter d'autres tâches, réalisant ainsi la concurrence au sein d'un seul thread.
Utilisation de base d'asyncio
1. Exécution d'une coroutine
Pour exécuter une coroutine, vous pouvez utiliser laasyncio.run()fonction. Elle crée une boucle d'événements et exécute la coroutine spécifiée.
Exemple
async def main():
print("Start")
await asyncio.sleep(1)
print("End")
asyncio.run(main())
2. Exécution de plusieurs tâches en parallèle
Vous pouvez utiliser laasyncio.gather()fonction pour exécuter plusieurs coroutines en parallèle et attendre qu'elles se terminent toutes.
Exemple
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
async def main():
await asyncio.gather(task1(), task2())
asyncio.run(main())
3. Contrôle des délais d'attente
Vous pouvez utiliser laasyncio.wait_for()fonction pour définir un délai d'attente pour une coroutine. Si la coroutine ne se termine pas dans le délai spécifié, uneasyncio.TimeoutErrorexception sera levée.
Exemple
async def long_task():
await asyncio.sleep(10)
print("Tâche terminée")
async def main():
try:
await asyncio.wait_for(long_task(), timeout=5)
except asyncio.TimeoutError:
print("La tâche a expiré")
asyncio.run(main())
Scénarios d'application d'asyncio
asyncioParticulièrement adapté aux scénarios suivants :
- Requêtes réseau: telles que les requêtes HTTP, la communication WebSocket, etc.
- E/S de fichiers: comme la lecture et l'écriture asynchrones de fichiers.
- Opérations de base de données: telles que l'accès asynchrone aux bases de données.
- Traitement de données en temps réel: tel que le traitement des files d'attente de messages en temps réel.
Classes, méthodes et fonctions courantes
1. Fonctions de base
| Méthode/Fonction | Description | Exemple |
|---|---|---|
asyncio.run(coro) |
Exécuter la fonction principale asynchrone (Python 3.7+) | asyncio.run(main()) |
asyncio.create_task(coro) |
Créer une tâche et l'ajouter à la boucle d'événements | task = asyncio.create_task(fetch_data()) |
asyncio.gather(*coros) |
Exécuter plusieurs coroutines de manière concurrente | await asyncio.gather(task1, task2) |
asyncio.sleep(delay) |
Attente asynchrone (non bloquante) | await asyncio.sleep(1) |
asyncio.wait(coros) |
Contrôler la façon dont les tâches se terminent | done, pending = await asyncio.wait([task1, task2]) |
2. Boucle d'événements
| Méthode | Description | Exemple |
|---|---|---|
loop.run_until_complete(future) |
Exécuter jusqu'à ce que la tâche se termine | loop.run_until_complete(main()) |
loop.run_forever() |
Exécuter la boucle d'événements indéfiniment | loop.run_forever() |
loop.stop() |
Arrêter la boucle d'événements | loop.stop() |
loop.close() |
Fermer la boucle d'événements | loop.close() |
loop.call_soon(callback) |
Planifier une fonction de rappel à exécuter immédiatement | loop.call_soon(print, "Hello") |
loop.call_later(delay, callback) |
Exécuter un callback avec un délai | loop.call_later(5, callback) |
3. Coroutines et tâches
| Méthode/Décorateur | Description | Exemple |
|---|---|---|
@asyncio.coroutine |
Décorateur de coroutine (hérité, Python 3.4-3.7) | @asyncio.coroutine def old_coro(): |
async def |
Définir une coroutine (Python 3.5+) | async def fetch(): |
task.cancel() |
Annuler une tâche | task.cancel() |
task.done() |
Vérifier si une tâche est terminée | if task.done(): |
task.result() |
Obtenir le résultat de la tâche (nécessite que la tâche soit terminée) | data = task.result() |
4. Primitives de synchronisation (similaires àthreading)
| Classe | Description | Exemple |
|---|---|---|
asyncio.Lock() |
Verrou mutex asynchrone | lock = asyncio.Lock() async with lock: |
asyncio.Event() |
Notification d'événement | event = asyncio.Event() await event.wait() |
asyncio.Queue() |
File d'attente asynchrone | queue = asyncio.Queue() await queue.put(item) |
asyncio.Semaphore() |
Semaphore | sem = asyncio.Semaphore(5) async with sem: |
5. Réseautage et sous-processus
| Méthode/Classe | Description | Exemple |
|---|---|---|
asyncio.open_connection() |
Établir une connexion TCP | reader, writer = await asyncio.open_connection('host', 80) |
asyncio.start_server() |
Créer un serveur TCP | server = await asyncio.start_server(handle, '0.0.0.0', 8888) |
asyncio.create_subprocess_exec() |
Créer un sous-processus | proc = await asyncio.create_subprocess_exec('ls') |
6. Outils utilitaires
| Méthode | Description | Exemple |
|---|---|---|
asyncio.current_task() |
Obtenir la tâche actuelle | task = asyncio.current_task() |
asyncio.all_tasks() |
Obtenir toutes les tâches | tasks = asyncio.all_tasks() |
asyncio.shield(coro) |
Protéger une tâche contre l'annulation | await asyncio.shield(critical_task) |
asyncio.wait_for(coro, timeout) |
Attendre avec un délai d'attente | try: await asyncio.wait_for(task, 5) |
Exemple
1. Exemple de coroutine de base
Exemple
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(hello()) # Python 3.7+
2. Exécution concurrente de tâches
Exemple
print(f"Récupération de {url}")
await asyncio.sleep(2)
return f"Données de {url}"
async def main():
results = await asyncio.gather(
fetch("url1.com"),
fetch("url2.com")
)
print(results)
asyncio.run(main())
3. Utilisation d'une file d'attente asynchrone
Exemple
for i in range(5):
await queue.put(i)
await asyncio.sleep(0.1)
async def consumer(queue):
while True:
item = await queue.get()
print(f"Consumed {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(
producer(queue),
consumer(queue)
)
Notes
-
Version de Python: Certaines fonctionnalités nécessitent Python 3.7+ (comme
asyncio.run())。 -
Opérations bloquantes: Évitez d'utiliser du code bloquant synchrone dans les coroutines (comme
time.sleep())。 -
Débogage: Définissez la
PYTHONASYNCIODEBUG=1variable d'environnement pour activer le mode débogage. -
Annulation de tâches: Une tâche annulée lèvera
CancelledError, ce qui doit être géré correctement.