Изучение asyncio: ошибка предупреждения «сопрограмма никогда не ожидалась» - PullRequest
0 голосов
/ 30 января 2019

Я пытаюсь научиться использовать asyncio в Python для оптимизации скриптов.Мой пример возвращает предупреждение coroutine was never awaited, можете ли вы помочь понять и найти, как его решить?

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")

Первая классическая часть кода работает правильно, но вторая половина выдает только:

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

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Вы сделали faire_toutes_les_requetes_sans_bloquer функцию ожидаемой , сопрограмму, используя async def.

Когда вы вызываете ожидаемую функцию, вы создаете новый объект сопрограммы.Код внутри функции не будет работать до тех пор, пока вы не дождетесь функции или не запустите ее как задачу:

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

Вы хотите сохранить эту функцию синхронной , потому что вы не запускаете цикл до тех пор, пока внутри этой функции:

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

Однако вы также пытаетесь использовать объект aiophttp.ClientSession(), а это асинхронный менеджер контекста ожидается, что вы будете использовать его с async with, а не только с with, и поэтому должны выполняться в стороне от ожидаемой задачи.Если вы используете with вместо async with, будет сгенерировано исключение TypeError("Use async with instead").

Все это означает, что вам нужно переместить loop.run_until_complete() вызов из вашего faire_toutes_les_requetes_sans_bloquer()функция, так что вы можете оставить это в качестве основной задачи для запуска;Вы можете позвонить и дождаться asycio.gather() напрямую, затем:

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")

Я использовал новую asyncio.run() функцию (Python 3.7 и выше) для запуска единственной главной задачи.Это создает выделенный цикл для этой сопрограммы верхнего уровня и запускает ее до завершения.

Далее необходимо переместить закрывающую скобку ) в выражении await resp.json():

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

Вы хотите получить доступ к клавише 'uuid' по результату await, а не к сопрограмме, которую создает response.json().

С этими изменениями ваш код работает, но версия asyncio завершается за секундувремя;Вы можете распечатать микросекунды:

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

На моем аппарате синхронный код requests примерно за 4-5 секунд, а код asycio завершится менее чем за 0,5 секунды.

0 голосов
/ 30 января 2019

Не используйте loop.run_until_complete вызов внутри async функции.Цель этого метода - запустить асинхронную функцию в контексте синхронизации.В любом случае вот как вы должны изменить код:

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())

Обратите внимание, что один faire_toutes_les_requetes_sans_bloquer() вызов создает будущее, которое должно ожидаться через явный await (для этого вы должны быть внутри asyncконтекст) или передается в некоторый цикл событий.Оставшись один, Питон жалуется на это.В исходном коде вы ничего этого не делаете.

...