Comment utiliser asyncio en Python

La fonctionnalité de programmation asynchrone de Python, ou async pour faire court, vous permet d'écrire des programmes qui font plus de travail en n'attendant pas la fin des tâches indépendantes. La asynciobibliothèque incluse avec Python vous donne les outils pour utiliser async pour traiter les E / S disque ou réseau sans faire attendre tout le reste.

asyncio fournit deux types d'API pour traiter les opérations asynchrones: de  haut niveau  et de  bas niveau . Les API de haut niveau sont les plus généralement utiles et elles s'appliquent à la plus grande variété d'applications. Les API de bas niveau sont puissantes, mais également complexes, et utilisées moins fréquemment.

Nous allons nous concentrer sur les API de haut niveau dans cet article. Dans les sections ci-dessous, nous allons parcourir les API de haut niveau les plus couramment utilisées  asyncioet montrer comment elles peuvent être utilisées pour des opérations courantes impliquant des tâches asynchrones. 

Si vous êtes complètement nouveau dans l'async en Python, ou si vous pouvez utiliser un rappel sur son fonctionnement, lisez mon introduction à Python async avant de plonger ici.

Exécuter des coroutines et des tâches en Python

Naturellement, l'utilisation la plus courante asyncioest d'exécuter les parties asynchrones de votre script Python. Cela signifie apprendre à travailler avec des coroutines et des tâches. 

Les composants asynchrones de Python, y compris les coroutines et les tâches, ne peuvent être utilisés qu'avec d'autres composants asynchrones, et non avec Python synchrone conventionnel, vous devez  asyncio donc combler le fossé. Pour ce faire, vous utilisez la  asyncio.run fonction:

importer asyncio

async def main ():

print ("Attente de 5 secondes.")

pour _ dans la plage (5):

attendre asyncio.sleep (1)

impression (".")

print ("Attente terminée.")

asyncio.run (main ())

Cela s'exécute  main(), avec toutes les coroutines,  main() se déclenche et attend le retour du résultat.

En règle générale, un programme Python ne doit avoir qu'une seule  .run() instruction, tout comme un programme Python ne doit avoir qu'une seule  main() fonction. Async, s'il est utilisé de manière imprudente, peut rendre le flux de contrôle d'un programme difficile à lire. Avoir un seul point d'entrée vers le code asynchrone d'un programme empêche les choses de devenir velues.

Les fonctions asynchrones peuvent également être planifiées comme des  tâches ou des objets qui encapsulent des coroutines et aident à les exécuter.

async def my_task ():

faire quelque chose()

tâche = asyncio.create_task (ma_tâche ())

my_task() est ensuite exécuté dans la boucle d'événements, avec ses résultats stockés dans  task.

Si vous n'avez qu'une seule tâche dont vous souhaitez obtenir des résultats, vous pouvez utiliser  asyncio.wait_for(task) pour attendre la fin de la tâche, puis utiliser  task.result() pour récupérer son résultat. Mais si vous avez planifié un certain nombre de tâches à exécuter et que vous souhaitez attendre   qu'elles soient toutes terminées, utilisez  asyncio.wait([task1, task2]) pour collecter les résultats. (Notez que vous pouvez définir un délai d'expiration pour les opérations si vous ne souhaitez pas qu'elles s'exécutent au-delà d'une certaine durée.)

Gérer une boucle d'événements asynchrones en Python

Une autre utilisation courante  asyncio est de gérer la boucle d'événements asynchrones  . La boucle d'événements est un objet qui exécute des fonctions asynchrones et des rappels; il est créé automatiquement lorsque vous utilisez  asyncio.run(). Vous voulez généralement utiliser une seule boucle d'événements asynchrones par programme, encore une fois pour que les choses restent gérables.

Si vous écrivez un logiciel plus avancé, tel qu'un serveur, vous aurez besoin d'un accès de niveau inférieur à la boucle d'événements. Pour cela, vous pouvez «soulever le capot» et travailler directement avec les composants internes de la boucle d'événement. Mais pour les travaux simples, vous n'en aurez pas besoin.

Lire et écrire des données avec des flux en Python

Les meilleurs scénarios pour asynchrone sont les opérations réseau de longue durée, où l'application peut bloquer l'attente d'une autre ressource pour renvoyer un résultat. À cette fin,  asyncio propose des flux, qui sont des mécanismes de haut niveau pour effectuer des E / S réseau. Cela comprend le fait d'agir en tant que serveur pour les requêtes réseau.

asyncio utilise deux classes,  StreamReader et  StreamWriter, pour lire et écrire à partir du réseau à un niveau élevé. Si vous souhaitez lire à partir du réseau, vous utiliserez  asyncio.open_connection() pour ouvrir la connexion. Cette fonction renvoie un tuple d'   objets StreamReader et  StreamWriter, et vous utiliseriez des   méthodes .read() et  .write()sur chacun pour communiquer.

Pour recevoir des connexions d'hôtes distants, utilisez  asyncio.start_server(). La asyncio.start_server()fonction prend comme argument une fonction de rappel  client_connected_cb, qui est appelée chaque fois qu'elle reçoit une requête. Cette fonction de rappel prend des instances de  StreamReader et StreamWriter comme arguments, vous pouvez donc gérer la logique de lecture / écriture pour le serveur. (Voir ici un exemple de serveur HTTP simple qui utilise la   bibliothèque asyncio-driven  aiohttp.)

Synchroniser les tâches en Python

Les tâches asynchrones ont tendance à s'exécuter de manière isolée, mais vous souhaiterez parfois qu'elles communiquent entre elles. asyncio fournit des files d'attente et plusieurs autres mécanismes de synchronisation entre les tâches:

  • Files d'attente : les  asyncio files d'attente permettent aux fonctions asynchrones d'aligner les objets Python à consommer par d'autres fonctions asynchrones - par exemple, pour répartir les charges de travail entre différents types de fonctions en fonction de leurs comportements.
  • Primitives de synchronisation : les verrous, les événements, les conditions et les sémaphores asynciofonctionnent comme leurs homologues Python conventionnels. 

Une chose à garder à l'esprit à propos de toutes ces méthodes est qu'elles ne sont  pas  thread-safe. Ce n'est pas un problème pour les tâches asynchrones exécutées dans la même boucle d'événements. Mais si vous essayez de partager des informations avec des tâches dans une boucle d'événements, un thread de système d'exploitation ou un processus différent, vous devrez utiliser le  threading module et ses objets pour le faire.

De plus, si vous souhaitez  lancer des  coroutines au-delà des limites de thread, utilisez la  asyncio.run_coroutine_threadsafe() fonction et transmettez la boucle d'événements à utiliser avec elle en tant que paramètre.

Suspendre une coroutine en Python

Une autre utilisation courante de  asyncio, et sous-discutée, est l'attente d'une durée arbitraire dans une coroutine. Vous ne pouvez pas utiliser  time.sleep() pour cela, ou vous bloquerez tout le programme. À la place, utilisez  asyncio.sleep(), ce qui permet à d'autres coroutines de continuer à fonctionner.

Utiliser une asynchrone de niveau inférieur en Python

Enfin, si vous pensez que l'application que vous créez peut nécessiter asynciodes composants de niveau inférieur, regardez autour de vous avant de commencer à coder: il y a de fortes chances que quelqu'un ait déjà construit une bibliothèque Python asynchrone qui fait ce dont vous avez besoin.

Par exemple, si vous avez besoin de requêtes DNS asynchrones, vérifiez la  aiodns bibliothèque et pour les sessions SSH asynchrones, il y a  asyncSSH. Recherchez PyPI par le mot-clé «async» (ainsi que d'autres mots-clés liés aux tâches) ou consultez la liste Awesome Asyncio organisée à la main pour des idées.