111 votes

Apprendre asyncio : Erreur d'avertissement "coroutine was never awaited" (la coroutine n'a jamais été attendue)

J'essaie d'apprendre à utiliser asyncio en Python pour optimiser les scripts. Mon exemple renvoie un coroutine was never awaited avertissement, pouvez-vous aider à comprendre et à trouver comment le résoudre ?

import time 
import datetime
import random
import asyncio

import aiohttp
import requests

def requete_bloquante(num):
    print(f'Get {num}')
    uid = requests.get("https://httpbin.org/uuid").json()['uuid']
    print(f"Res {num}: {uid}")

def faire_toutes_les_requetes():
    for x in range(10):
        requete_bloquante(x)

print("Bloquant : ")
start = datetime.datetime.now()
faire_toutes_les_requetes()
exec_time = (datetime.datetime.now() - start).seconds
print(f"Pour faire 10 requêtes, ça prend {exec_time}s\n")

async def requete_sans_bloquer(num, session):
    print(f'Get {num}')
    async with session.get("https://httpbin.org/uuid") as response:
        uid = (await response.json()['uuid'])
    print(f"Res {num}: {uid}")

async def faire_toutes_les_requetes_sans_bloquer():
    loop = asyncio.get_event_loop()
    with aiohttp.ClientSession() as session:
        futures = [requete_sans_bloquer(x, session) for x in range(10)]
        loop.run_until_complete(asyncio.gather(*futures))
    loop.close()
    print("Fin de la boucle !")

print("Non bloquant : ")
start = datetime.datetime.now()
faire_toutes_les_requetes_sans_bloquer()
exec_time = (datetime.datetime.now() - start).seconds
print(f"Pour faire 10 requêtes, ça prend {exec_time}s\n")

Le premier classique La partie du code s'exécute correctement, mais la seconde moitié produit seulement :

synchronicite.py:43: RuntimeWarning: coroutine 'faire_toutes_les_requetes_sans_bloquer' was never awaited

1 votes

Pourquoi avez-vous utilisé faire_toutes_les_requetes_sans_bloquer() à la fin de votre module ? Cet appel crée l'objet à attendre (une coroutine), mais vous n'avez jamais await sur elle.

119voto

Martijn Pieters Points 271458

Vous avez fait faire_toutes_les_requetes_sans_bloquer un à attendre une coroutine, en utilisant async def .

Lorsque vous appelez une fonction attendable, vous créez un nouvel objet coroutine. Le code à l'intérieur de la fonction ne s'exécutera pas tant que vous n'aurez pas ensuite attendre sur la fonction ou l'exécuter comme une tâche :

>>> async def foo():
...     print("Running the foo coroutine")
...
>>> foo()
<coroutine object foo at 0x10b186348>
>>> import asyncio
>>> asyncio.run(foo())
Running the foo coroutine

Vous voulez garder cette fonction synchrone car vous ne commencez la boucle qu'à l'intérieur de cette fonction :

def faire_toutes_les_requetes_sans_bloquer():
    loop = asyncio.get_event_loop()
    # ...
    loop.close()
    print("Fin de la boucle !")

Cependant, vous essayez également d'utiliser un aiophttp.ClientSession() et c'est un gestionnaire de contexte asynchrone vous êtes censé l'utiliser avec async with pas seulement with et doit donc être exécuté à côté d'une tâche attendable. Si vous utilisez with au lieu de async with a TypeError("Use async with instead") l'exception sera levée.

Cela signifie que vous devez déplacer le loop.run_until_complete() appelez out de votre faire_toutes_les_requetes_sans_bloquer() de sorte que vous pouvez la conserver comme tâche principale à exécuter ; vous pouvez appeler et attendre la fonction asycio.gather() directement alors :

async def faire_toutes_les_requetes_sans_bloquer():
    async with aiohttp.ClientSession() as session:
        futures = [requete_sans_bloquer(x, session) for x in range(10)]
        await asyncio.gather(*futures)
    print("Fin de la boucle !")

print("Non bloquant : ")
start = datetime.datetime.now()
loop.run(faire_toutes_les_requetes_sans_bloquer())
exec_time = (datetime.datetime.now() - start).seconds
print(f"Pour faire 10 requêtes, ça prend {exec_time}s\n")

J'ai utilisé le nouveau asyncio.run() función (Python 3.7 et plus) pour exécuter la seule tâche principale. Cela crée une boucle dédiée pour cette coroutine de niveau supérieur et l'exécute jusqu'à ce qu'elle soit terminée.

Ensuite, vous devez déplacer la fermeture ) parenthèse sur le await resp.json() expression :

uid = (await response.json())['uuid']

Vous voulez accéder au 'uuid' sur le résultat de la await et non la coroutine qui response.json() produit.

Avec ces modifications, votre code fonctionne, mais la version asyncio se termine en moins d'une seconde ; vous voudrez peut-être imprimer des microsecondes :

exec_time = (datetime.datetime.now() - start).total_seconds()
print(f"Pour faire 10 requêtes, ça prend {exec_time:.3f}s\n")

Sur ma machine, le synchrone requests en 4 à 5 secondes environ, et le code asycio se termine en moins de 0,5 seconde.

8voto

freakish Points 20067

Ne pas utiliser loop.run_until_complete appel à l'intérieur async fonction. Le but de cette méthode est d'exécuter une fonction asynchrone dans un contexte synchrone. Quoi qu'il en soit, voici comment vous devez modifier le code :

async def faire_toutes_les_requetes_sans_bloquer():
    async with aiohttp.ClientSession() as session:
        futures = [requete_sans_bloquer(x, session) for x in range(10)]
        await asyncio.gather(*futures)
    print("Fin de la boucle !")

loop = asyncio.get_event_loop()
loop.run_until_complete(faire_toutes_les_requetes_sans_bloquer())

Notez que seul faire_toutes_les_requetes_sans_bloquer() crée un futur qui doit être attendu soit par le biais de l'appel explicite await (pour cela, vous devez être à l'intérieur async ) ou transmis à une boucle d'événements. Lorsqu'il est laissé seul, Python se plaint de cela. Dans votre code original, vous ne faites rien de tout cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X