Erreurs ‘asyncio’ courantes en Python, comment les éviter ?

Le module Asyncio est une API permettant de faire de la programmation asynchrone en Python avec l’utilisation de coroutines. La mise en œuvre de cette API peut être frustrant pour les débutants. La raison en est une série d’erreurs courantes commises lors de la programmation des coroutines avec l’API Asyncio.

Dans ce tutoriel, vous découvrirez les erreurs les plus courantes rencontrées par les débutants avec Python Asyncio et comment les résoudre.

Table des matières

En première approche, l’exécution des coroutines Python de manière concurrente et d’avoir le contrôle total sur leur exécution, peut être déstabilisant pour  les personnes commençant le développement d’application asynchrone en langage Python. En effet les coroutines ressemblent à des fonctions mais ne sont pas utilisées comme des fonctions. Cela porte à confusion.

Les erreurs fréquemment rencontrées lors du développement avec Asyncio en Python sont :

Essayer d’exécuter des coroutines en les appelants comme une fonction.

C’est l’erreur la plus fréquemment rencontrée par les débutants. Elle consiste à appeler une coroutine asyncio comme une fonction.

Par exemple, nous pouvons définir une coroutine en utilisant l’expression async def :

async def custom_coroutine():
   print('hello from custom_coroutine')

Le débutant tentera alors d’appeler cette coroutine comme une fonction et s’attendra à ce que le message hello from custom_coroutine s’affiche.

# Tentative d'erreur pour appeler une coroutine comme une fonction
custom_coroutine()

Appeler une coroutine comme une fonction n’exécutera pas le corps de la coroutine mais crée un objet Python coroutine. Pour en savoir plus sur les coroutines, aller consulter  la documentation Python Coroutines et tâches. Cet objet coroutine  peut alors être attendu dans le runtime asyncio, avec une boucle d’événement. La boucle d’événements pouvant être démarrée, à l’aide de la fonction asyncio.run(), afin d’exécuter la coroutine.

Par exemple :

# Exécute une coroutine
asyncio.run(custom_coroutine())

Alternativement, nous pouvons planifier l’exécution d’une coroutine, sur un objet awaitable, en utilisant l’expression await à la condition d’être utilisée à l’intérieur d’une coroutine.

Par exemple :

# Exécute une coroutine
await custom_coroutine()

Ne pas laisser les coroutines s’exécuter dans la boucle d’événements.

Erreur très courante qui est de ne pas exécuter une coroutine. En effet si une coroutine n’est pas exécutée, vous recevrez un message d’erreur suivant :

RuntimeWarning: coroutine 'custom_coroutine' was never awaited

Cela se produira si vous créez un objet coroutine mais que vous ne planifiez pas son exécution dans la boucle d’événement asyncio.

Vous pouvez exécuter la coroutine, comme nous l’avons vu dans la section précédente, en démarrant la boucle d’événement asyncio. Par exemple 

# Création d’un objet coroutine
coro = custom_coroutine()
# Exécution de la coroutine
asyncio.run(coro)

Ou, plus simplement dans une instruction composée :

asyncio.run(custom_coroutine())

Si vous obtenez cette erreur à l’intérieur d’une fonction coroutine, c’est parce que vous avez créé une coroutine et que vous n’avez pas programmé son exécution avec la méthode await.

async def myfunc():
    coro = custom_coroutine()
    await coro()

Ou, vous pouvez programmer pour que cette coroutine s’exécute indépendamment en tant que tâche en utilisant la méthode create_task

# Création d’un objet coroutine
coro = custom_coroutine()
# Exécution de la coroutine
asyncio.create_task(coro)

Utilisation de l’API asyncio de bas niveau.

Un autre problème étant que de nombreux développeurs débutants programme avec la mauvaise API Asyncio en utilisant des bibliothèques de bas-niveau.

En effet Asyncio propose deux API .

  • API de haut niveau pour les développeurs d’applications.
  • API de bas niveau pour les développeurs de frameworks et de bibliothèques python.

Cette erreur est courante pour un certain nombre de raisons.

  • L’API a beaucoup changé avec les versions récentes de Python.
  • La page de documentation de l’API asyncio  rend les choses confuses, en montrant les deux API.
  • Beaucoup d’exemples utilisent cette API de bas niveau sans que cela soit nécessaire.

L’utilisation de l’API de bas niveau  rend le code Python plus détaillé, plus difficile à maintenir et beaucoup moins compréhensible. Nous devrions presque toujours nous en tenir à l’API de haut niveau et surtout lorsque l’on débute.

En conclusion, tout débutant doit utiliser l’API de haut niveau pendant un certain temps afin de se  familiarisez-avec la programmation asynchrone et exécutez des coroutines à volonté. Une fois le sujet maitrisé, il sera alors possible de jeter un coup d’œil à l’API de bas niveau.

Quitter la coroutine principale trop tôt.

Un autre point de confusion majeur dans la programmation avec asyncio étant  de ne pas donner suffisamment de temps aux coroutines pour s’exécuter.

Nous avons vu que nous pouvons programmer de nombreuses coroutines pour qu’elles s’exécutent indépendamment dans un programme asyncio via la méthode asyncio.create_task() .

La coroutine principale, point d’entrée du programme asyncio, peut alors poursuivre d’autres activités. Si la coroutine principale se termine, le programme asyncio se terminera même s’il y a une ou plusieurs coroutines qui sont en cours d’exécution, indépendamment en tant que tâches.

Pour éviter cela si la coroutine principale n’a rien d’autre à faire, elle doit attendre les tâches restantes. Ceci peut être réalisé en obtenant d’abord un ensemble de toutes les tâches en cours d’exécution via la fonction asyncio.all_tasks(), en se retirant de cet ensemble, puis en attendant les tâches restantes via la méthode asyncio.wait().

# Obtenir l’ensemble de toutes les tâches en cours d’éxécution
all_tasks = asyncio.all_tasks()
# Obtenir la tache courante
current_task = asyncio.current_task()
# Suppresion de la tache courante de la liste des taches en cours
all_tasks.remove(current_task)
# Attente jusqu'à ce que toutes les autres tâches soient terminées
await asyncio.wait(all_tasks)

Autres lectures

Laisser un commentaire