学习异步:“从不等待协同程序”警告

2024-04-20 03:36:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图学习在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

Tags: importasynciouiddatetimetimedefnumles
2条回答

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

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独自一人抱怨的时候。在你的原始代码中,你没有做这些。

您使用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()
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")

我使用新的^{} function(Python 3.7及更高版本)来运行单个主任务。这将为顶级协程创建一个专用循环并运行它直到完成。

接下来,需要移动await resp.json()表达式上的结束)括号:

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

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

通过这些更改,您的代码可以工作,但异步版本在次秒时间内完成;您可能需要打印微秒:

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秒的时间内完成。

相关问题 更多 >