【问题标题】:Learning asyncio: "coroutine was never awaited" warning error学习 asyncio:“coroutine was never awaited”警告错误
【发布时间】:2019-06-23 18:47:50
【问题描述】:

我正在尝试学习在 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")

classic部分代码运行正确,但后半部分只产生:

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

【问题讨论】:

  • 为什么在模块末尾使用faire_toutes_les_requetes_sans_bloquer()?该调用创建了可等待对象(协程),但您从未在其上使用await

标签: python python-asyncio aiohttp


【解决方案1】:

您通过使用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") 异常。

这一切都意味着您需要将 faire_toutes_les_requetes_sans_bloquer() 函数的 loop.run_until_complete() 调用 out 移出,这样您就可以将其作为要运行的主要任务;您可以直接在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() function(Python 3.7 及更高版本)来运行单个主任务。这会为该顶级协程创建一个专用循环并运行它直到完成。

接下来,您需要在await resp.json() 表达式上移动右括号)

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

您想访问await 的结果上的'uuid' 键,而不是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 秒内完成。

【讨论】:

    【解决方案2】:

    不要在async 函数中使用loop.run_until_complete 调用。该方法的目的是在同步上下文中运行异步函数。无论如何,这是您应该如何更改代码:

    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 会抱怨这一点。在您的原始代码中,您什么都不做。

    【讨论】:

    • 别忘了import asyncio和开头
    • 注意:这会引发TypeError("Use async with instead"),因为ClientSession() 是一个异步上下文管理器。
    • @MartijnPieters 很公平,已修复。
    • 抛出 RuntimeError: This event loop is already running
    猜你喜欢
    • 2022-08-24
    • 1970-01-01
    • 2020-12-01
    • 2022-09-25
    • 1970-01-01
    • 2017-12-14
    • 2017-01-01
    • 2016-11-09
    • 2021-02-15
    相关资源
    最近更新 更多