学习asyncio:“coroutine was never awaited”警告错误

新手上路,请多包涵

我正在尝试学习在 Python 中使用 asyncio 来优化脚本。我的例子返回一个 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

原文由 Anthony Hervy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 7.9k
2 个回答

您使用 async def 制作了 faire_toutes_les_requetes_sans_bloquer 一个 等待 函数,一个协程。

当你调用一个等待函数时,你创建了一个新的协程对象。函数内的代码不会运行,直到您 等待 函数或将其作为任务运行:

 >>> 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()
asyncio.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 秒内完成。

原文由 Martijn Pieters 发布,翻译遵循 CC BY-SA 4.0 许可协议

不要使用 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 中等待某些上下文)事件循环。当独自一人时,Python 会抱怨。在您的原始代码中,您什么都不做。

原文由 freakish 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题