3.1 Module asyncio
Créer ses propres threads pour des sous-tâches asynchrones, plus personne ne fait ça. Enfin, on pourrait, mais ces actions sont considérées comme trop bas niveau et ne sont utilisées que par les développeurs de frameworks. Et encore, seulement quand c'est vraiment indispensable.
La mode aujourd'hui est à la
programmation asynchrone, les opérateurs async/await
et les coroutines avec tasks. Mais parlons-en dans l'ordre...
Un peu d'histoire
Au départ, en Python, pour résoudre des tâches de programmation asynchrone, on utilisait des coroutines basées sur des générateurs.
Ensuite, avec Python 3.4, un module
asyncio
(parfois écrit async
IO
) est apparu, mettant en œuvre des mécanismes de programmation asynchrone.
En Python 3.5 est apparue la construction async/await
.
Maintenant, un peu d'introduction. Je vais d'abord te parler brièvement de toutes ces choses, puis plus en détail. Parce que presque tout fonctionne ensemble, et il est impossible d'expliquer un concept sans faire référence aux autres.
Module asyncio
Le module asyncio
est destiné à écrire
des programmes asynchrones, offrant la possibilité d'exécuter des tâches
en parallèle. Il prend en charge les opérations d'entrée-sortie asynchrones,
les minuteries, les sockets, l'exécution de coroutines et le multithreading,
fonctionnant dans une ou plusieurs boucles d'événements.
Coroutines (Coroutines)
Les coroutines sont des
fonctions asynchrones, définies avec le mot-clé
async def
. Les coroutines permettent de
suspendre leur exécution avec le mot-clé
await
, ce qui
permet à d'autres coroutines de s'exécuter pendant ce temps
.
import asyncio
# Définition d'une fonction asynchrone (coroutine)
async def main():
print('Hello ...')
# Suspend l'exécution pendant 1 seconde
await asyncio.sleep(1)
print('... World!')
# Lance la fonction asynchrone main() dans la boucle d'événements
asyncio.run(main())
Boucle d'événements (Event Loop)
La boucle d'événements gère l'exécution des coroutines, des tâches et d'autres
opérations asynchrones. L'appel de asyncio.run()
lance la boucle d'événements et
exécute la coroutine jusqu'à sa terminaison.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# Obtenir la boucle d'événements actuelle
loop = asyncio.get_event_loop()
# Exécuter la coroutine jusqu'à sa fin
loop.run_until_complete(main())
# Fermer la boucle d'événements après la fin de toutes les tâches
loop.close()
Tâches (Tasks)
Les tâches permettent d'exécuter des coroutines en parallèle. Elles sont créées avec
asyncio.create_task()
ou
asyncio.ensure_future()
.
import asyncio
# Définition d'une coroutine qui sera exécutée avec un délai
async def say_after(delay, what):
# Suspend l'exécution pour un délai donné
await asyncio.sleep(delay)
print(what)
# Coroutine principale
async def main():
# Créer des tâches pour exécuter les coroutines en parallèle
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# Attendre la fin des deux tâches
await task1
await task2
# Lance la coroutine principale
asyncio.run(main())
Futures (Futures)
Les objets Future
représentent les résultats des opérations asynchrones
qui seront disponibles à l'avenir. Par exemple, ils sont utilisés pour
attendre la fin d'une tâche asynchrone.
import asyncio
# Définition d'une coroutine qui simule une longue tâche
async def long_running_task():
print('Task started')
# Suspend l'exécution pendant 3 secondes
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# Coroutine principale
async def main():
# Créer un future pour attendre la fin de la tâche
future = asyncio.ensure_future(long_running_task())
# Attendre la fin de la tâche et obtenir le résultat
result = await future
print(f'Task result: {result}')
# Lance la coroutine principale
asyncio.run(main())
3.2 Fonction asynchrone — async def
Une fonction asynchrone se déclare comme une fonction normale, mais avant
le mot-clé def
, il faut écrire le mot
async
.
async def NomDeLaFonction(paramètres):
code de la fonction
Une fonction asynchrone se déclare comme une normale, se déclenche comme une normale, mais elle renvoie un résultat différent. Si tu appelles une fonction asynchrone, elle renverra non pas un résultat, mais un objet spécial — coroutine.
On peut même vérifier cela :
import asyncio
async def main():
print("Hello World")
# Appel de la fonction asynchrone, qui renvoie une coroutine
result = main()
# Vérifier le type du résultat
print(type(result)) # <class 'coroutine'>
Que se passe-t-il ? Lorsque tu annotes une fonction avec le mot async, tu lui ajoutes en fait un décorateur qui fait à peu près cela :
def async_decorator(func):
# Créer un objet Task
task = Task()
# Lui passer notre fonction func pour qu'il l'exécute
task.target = func
# Ajouter l'objet task à la file des tâches — Event Loop
eventloop.add_task(task)
# Retourner l'objet task
return task
Et ton code devient similaire à :
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
Le sens de cette analogie est le suivant :
Quand tu appelles une fonction asynchrone, un objet spécial Task
est créé, qui exécutera ta
fonction, mais à un moment dans le futur. Peut-être dans 0.0001 sec, ou peut-être dans 10.
Ce objet task
est ce qui te revient immédiatement en tant que résultat de l'appel de ta fonction asynchrone. C'est
la coroutine.
Ta fonction asynchrone peut-être n'a même pas encore commencé à être exécutée, et tu as déjà l'objet task
(coroutine).
Pourquoi as-tu besoin de ce task
(coroutine) ? Tu ne peux pas faire grand-chose avec, mais tu peux faire 3 choses :
- Attendre que la fonction asynchrone s'exécute.
- Attendre que la fonction asynchrone finisse de s'exécuter et obtenir le résultat de la fonction à partir de la coroutine.
- Attendre que 10 (ou un nombre quelconque) de fonctions asynchrones s'exécutent.
Je vais t'expliquer comment faire cela ci-dessous.
3.3 Opérateur await
La plupart des actions avec une coroutine commence par « attendre que la fonction asynchrone s'exécute ». C'est pourquoi pour cette
action, nous avons un opérateur spécial await
.
Il te suffit de l'écrire devant la coroutine :
await coroutine
Ou directement devant l'appel de la fonction asynchrone :
await fonction_asynchrone(arguments)
Quand Python rencontre dans le code l'opérateur await
, il suspend l'exécution de la fonction actuelle
et attend que
la coroutine se termine — jusqu'à ce que la fonction asynchrone à laquelle la coroutine fait référence soit terminée.
Important!
L'opérateur await
est utilisé uniquement à l'intérieur d'une fonction asynchrone pour suspendre l'exécution jusqu'à
ce que
la coroutine ou l'opération asynchrone soit terminée.
C'est fait pour simplifier le processus de commutation entre les appels de fonctions asynchrones. Un appel
await
— c'est en fait une déclaration « ici, nous allons attendre indéfiniment — occupez-vous d'exécuter les autres fonctions asynchrones ».
Exemple :
import asyncio
# Définition d'une fonction asynchrone
async def async_print(text):
print(text)
# Fonction asynchrone principale
async def main():
# Utilise await pour attendre l'exécution de la fonction asynchrone
await async_print("Hello World")
# Lance la boucle d'événements principale et exécute la coroutine main()
asyncio.run(main()) #lance la fonction asynchrone
En réalité, l'opérateur await
fonctionne de manière encore plus astucieuse — il retourne également le résultat de
l'exécution de la fonction asynchrone
sur laquelle il a été appelé.
Exemple :
import asyncio
# Définition d'une fonction asynchrone qui additionne deux nombres
async def async_add(a, b):
return a + b
# Fonction asynchrone principale
async def main():
# Utilise await pour obtenir le résultat de l'exécution de async_add
sum = await async_add(100, 200)
print(sum)
# Lance la boucle d'événements principale et exécute la coroutine main()
asyncio.run(main()) #lance la fonction asynchrone
Donc, en résumé, l'opérateur await
:
- Suspend la fonction asynchrone actuelle jusqu'à ce que la coroutine ou l'opération asynchrone soit terminée.
- Retourne le résultat de l'exécution de l'opération asynchrone ou de la coroutine.
- Ne peut être utilisé qu'à l'intérieur d'une fonction asynchrone.
GO TO FULL VERSION