Python 学习 asyncio:“从未等待过协程”警告错误

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/54441424/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 20:29:06  来源:igfitidea点击:

Learning asyncio: "coroutine was never awaited" warning error

pythonpython-asyncioaiohttp

提问by Anthony Hervy

I am trying to learn to use asyncio in Python to optimize scripts. My example returns a coroutine was never awaitedwarning, can you help to understand and find how to solve it?

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

The first classicpart of the code runs correctly, but the second half only produces:

代码的第一个经典部分正确运行,但后半部分只产生:

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

采纳答案by Martijn Pieters

You made faire_toutes_les_requetes_sans_bloqueran awaitablefunction, a coroutine, by usingasync def.

你让faire_toutes_les_requetes_sans_bloquer一个awaitable功能,协同程序,通过使用async def

When you call an awaitable function, you create a new coroutine object. The code inside the function won't run until you then awaiton the function or run it as a task:

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

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

You want to keep that function synchronous, because you don't start the loop until inside that function:

您想保持该函数同步,因为直到在该函数内部才开始循环:

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

However, you are also trying to use a aiophttp.ClientSession()object, and that's an asynchronous context manager, you are expected to use it with async with, not just with, and so has to be run in aside an awaitable task. If you use withinstead of async witha TypeError("Use async with instead")exception will be raised.

但是,您也在尝试使用一个aiophttp.ClientSession()对象,这是一个异步上下文管理器,您应该将它与 一起使用async with,而不仅仅是with,因此必须在等待的任务旁边运行。如果您使用with的不是async with一个TypeError("Use async with instead"),将引发异常。

That all means you need to move the loop.run_until_complete()call outof your faire_toutes_les_requetes_sans_bloquer()function, so you can keep that as the main task to be run; you can call and await on asycio.gather()directly then:

这一切都意味着您需要将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")

I used the new asyncio.run()function(Python 3.7 and up) to run the single main task. This creates a dedicated loop for that top-level coroutine and runs it until complete.

我使用了新asyncio.run()函数(Python 3.7 及更高版本)来运行单个主要任务。这会为该顶级协程创建一个专用循环并运行它直到完成。

Next, you need to move the closing )parenthesis on the await resp.json()expression:

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

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

You want to access the 'uuid'key on the result of the await, not the coroutine that response.json()produces.

您想访问 的'uuid'结果的键await,而不是response.json()产生的协程。

With those changes your code works, but the asyncio version finishes in sub-second time; you may want to print microseconds:

通过这些更改,您的代码可以工作,但 asyncio 版本在亚秒内完成;您可能想要打印微秒:

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

On my machine, the synchronous requestscode in about 4-5 seconds, and the asycio code completes in under .5 seconds.

在我的机器上,同步requests代码在大约 4-5 秒内完成,而 asycio 代码在 0.5 秒内完成。

回答by freakish

Do not use loop.run_until_completecall inside asyncfunction. The purpose for that method is to run an async function inside sync context. Anyway here's how you should change the code:

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

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

Note that alone faire_toutes_les_requetes_sans_bloquer()call creates a future that has to be either awaited via explicit await(for that you have to be inside asynccontext) or passed to some event loop. When left alone Python complains about that. In your original code you do none of that.

请注意,单独faire_toutes_les_requetes_sans_bloquer()调用会创建一个必须通过显式等待await(因为您必须在async上下文中)或传递给某个事件循环的未来。当独自一人时,Python 会抱怨这一点。在您的原始代码中,您什么都不做。