gpt4 book ai didi

python - asyncio.Task 中的 "async with"不工作

转载 作者:行者123 更新时间:2023-12-04 14:55:49 27 4
gpt4 key购买 nike

我正在学习 python asyncio 并使用它们测试大量代码。
下面是我尝试使用 asyncioaiohttp 订阅多个 Websocket 流的代码。

我不明白为什么当 coro(item1, item2): 作为任务执行时,它没有进入 async with ...堵塞。 (即打印“A”而不打印“B”)。
谁能帮我了解一下这是什么原因?

(我已经得到一个working code但是,我只是想了解这背后的机制是什么。)

代码

import aiohttp
import asyncio
import json

async def coro(
item1,
item2):
print("A")

async with aiohttp.ClientSession() as session:
async with session.ws_connect(url='URL') as ws:
print("B")

await asyncio.gather(ws.send_json(item1),
ws.send_json(item2))
print("C")

async for msg in ws:
print(msg)

async def ws_connect(item1,
item2):
task = asyncio.create_task(coro(item1, item2))
return task

async def main():

item1 = {
"method": "subscribe",
"params": {'channel': "..."}
}
item2 = {
"method": "subscribe",
"params": {'channel': "..."}
}

ws_task = await ws_connect(item1, item2)
print("D")

asyncio.run(main())

输出

D
A

最佳答案

B 永远不会被打印出来,因为您永远不会等待返回的任务,只会等待返回它的方法。

细微的错误在于 return task 之后是 await ws_connect(item1, item2)

TL;DR; 返回等待任务

理解程序输出的关键是要知道 asyncio 事件循环中的上下文切换只能发生在少数几个地方,特别是在 await 表达式中。此时,事件循环可能暂停当前协程并继续另一个协程。

首先,您创建一个 ws_connect 协程并立即等待它,这会强制事件循环挂起 main 并实际运行 ws_connect 因为那里没有其他要运行的东西。

由于 ws_connect 不包含任何允许上下文切换的点,因此 coro() 函数实际上从未启动。

create_task 唯一要做的就是将协程绑定(bind)到任务对象并将其添加到事件循环的队列中。但是你从不等待它,你只是把它作为任何普通的返回值返回。好的,现在 ws_connect() 完成并且事件循环可以选择运行任何任务,它选择继续 main 可能是因为它一直在等待 ws_connect().

好的,main 打印 D 并返回。现在怎么办?

asyncio.run 中有一些额外的 awaitcoro() 一个启动的机会——因此打印出 A (但仅在 D 之后)但没有任何东西强制 asyncio.run 等待 coro() 所以当 coro 返回到上下文循环通过 async withrun 完成,程序退出,这使得 coro() 未完成。

如果在 print('D') 之后添加额外的 await asyncio.sleep(1),循环将再次挂起 main至少一段时间,然后继续使用 coro(),如果 URL 正确,它将打印 B。

实际上,上下文切换有点复杂,因为协程上的普通 await 通常不会切换,除非执行确实需要阻塞 IO 或某些东西 await asyncio.sleep(0 )yield* 保证在没有额外阻塞的情况下真正的上下文切换。

* yield 来自 __await__ 方法。

这里的教训很简单 - 永远不要从 async 方法返回可等待对象,它会导致这种错误。默认情况下始终使用return await,最坏情况下,如果返回的对象实际上不是可等待的(如return await some_string),您会得到运行时错误并且它可以很容易地被发现并修复。

另一方面,从普通函数返回 awaitables 是可以的,并且让它表现得像函数是异步的。尽管混合使用这两种方法时应该小心。就个人而言,我更喜欢第一种方法,因为它将责任转移到函数的编写者身上,而不是将被警告 linters 的用户,后者通常会检测非等待的协程调用,但不会检测返回的等待对象。所以另一种解决方案是使ws_connect成为一个普通函数,然后await ws_connect中的await将应用于返回值(=任务),不是函数本身。

关于python - asyncio.Task 中的 "async with"不工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68044095/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com