"async with" asyncio.Task 内不工作
"async with" within asyncio.Task not working
我正在学习 python asyncio
并使用它们测试大量代码。
下面是我尝试使用 asyncio
和 aiohttp
.
订阅多个 Websocket 流媒体的代码
我不明白为什么当coro(item1, item2):
作为任务执行时,它没有进入async with ...
块。 (即打印“A”而不打印“B”)。
谁能帮我理解这是什么原因?
(我已经得到了但是,我只是想了解这背后的机制是什么。)
代码
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 永远不会被打印出来,因为你永远不会等待 returned 任务,只有 returned 它的方法。
微妙的错误在 return task
后面是 await ws_connect(item1, item2)
。
TL;DR; return await task
.
理解程序输出的关键是要知道 asyncio 事件循环中的上下文切换只能发生在少数几个地方,尤其是 await
表达式。此时,事件循环可能暂停当前协程并继续另一个。
首先,您创建一个 ws_connect
协程并立即等待它,这会强制事件循环暂停 main
并且实际上 运行 ws_connect
因为没有任何东西否则 运行.
由于 ws_connect
包含 none 允许上下文切换的点,因此 coro()
函数实际上从未启动。
create_task
唯一要做的就是将协程绑定到任务对象并将其添加到事件循环的队列中。但是你永远不会等待它,你只是 return 它就像任何普通的 return 值一样。好的,现在 ws_connect()
完成并且事件循环可以选择 运行 任何任务,它选择继续 main
可能是因为它一直在等待 ws_connect()
。
好的,main
打印 D 和 returns。现在怎么办?
在 asyncio.run
中有一些额外的 await
给 coro()
一个开始的机会 - 因此打印 A (但只有在D) 但没有任何强制 asyncio.run
等待 coro()
所以当 coro 通过 async with
返回到上下文循环时,run
完成并退出程序,留下 coro()
未完成。
如果在 print('D')
之后添加额外的 await asyncio.sleep(1)
,循环将再次暂停 main
至少一段时间,然后继续 coro()
,这将打印 B URL 是否正确。
实际上,上下文切换有点复杂,因为协程上的普通 await
通常不会切换,除非执行确实需要阻塞 IO 或其他东西 await asyncio.sleep(0)
或 yield
* 保证在没有额外阻塞的情况下进行真正的上下文切换。
*yield
来自 __await__
方法。
这里的教训很简单 - 永远不要从 async
方法中 return 等待,它会导致这种错误。默认情况下始终使用 return await
,最坏情况下你会得到 运行time 错误,以防 returned 对象实际上不可等待(如 return await some_string
)并且它很容易被发现和修复。
另一方面,return从普通函数中调用 awaitables 是可以的,并且让它表现得像函数是异步的。尽管混合使用这两种方法时应该小心。就个人而言,我更喜欢第一种方法,因为它将责任转移到函数的编写者身上,而不是将被警告的用户 linters 通常检测非等待的协程调用而不是 returned awaitables。所以另一种解决方案是使 ws_connect
成为一个普通函数,然后 await ws_connect
中的 await
将应用于 returned 值(=任务),而不是函数本身。
我正在学习 python asyncio
并使用它们测试大量代码。
下面是我尝试使用 asyncio
和 aiohttp
.
我不明白为什么当coro(item1, item2):
作为任务执行时,它没有进入async with ...
块。 (即打印“A”而不打印“B”)。
谁能帮我理解这是什么原因?
(我已经得到了
代码
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 永远不会被打印出来,因为你永远不会等待 returned 任务,只有 returned 它的方法。
微妙的错误在 return task
后面是 await ws_connect(item1, item2)
。
TL;DR; return await task
.
理解程序输出的关键是要知道 asyncio 事件循环中的上下文切换只能发生在少数几个地方,尤其是 await
表达式。此时,事件循环可能暂停当前协程并继续另一个。
首先,您创建一个 ws_connect
协程并立即等待它,这会强制事件循环暂停 main
并且实际上 运行 ws_connect
因为没有任何东西否则 运行.
由于 ws_connect
包含 none 允许上下文切换的点,因此 coro()
函数实际上从未启动。
create_task
唯一要做的就是将协程绑定到任务对象并将其添加到事件循环的队列中。但是你永远不会等待它,你只是 return 它就像任何普通的 return 值一样。好的,现在 ws_connect()
完成并且事件循环可以选择 运行 任何任务,它选择继续 main
可能是因为它一直在等待 ws_connect()
。
好的,main
打印 D 和 returns。现在怎么办?
在 asyncio.run
中有一些额外的 await
给 coro()
一个开始的机会 - 因此打印 A (但只有在D) 但没有任何强制 asyncio.run
等待 coro()
所以当 coro 通过 async with
返回到上下文循环时,run
完成并退出程序,留下 coro()
未完成。
如果在 print('D')
之后添加额外的 await asyncio.sleep(1)
,循环将再次暂停 main
至少一段时间,然后继续 coro()
,这将打印 B URL 是否正确。
实际上,上下文切换有点复杂,因为协程上的普通 await
通常不会切换,除非执行确实需要阻塞 IO 或其他东西 await asyncio.sleep(0)
或 yield
* 保证在没有额外阻塞的情况下进行真正的上下文切换。
*yield
来自 __await__
方法。
这里的教训很简单 - 永远不要从 async
方法中 return 等待,它会导致这种错误。默认情况下始终使用 return await
,最坏情况下你会得到 运行time 错误,以防 returned 对象实际上不可等待(如 return await some_string
)并且它很容易被发现和修复。
另一方面,return从普通函数中调用 awaitables 是可以的,并且让它表现得像函数是异步的。尽管混合使用这两种方法时应该小心。就个人而言,我更喜欢第一种方法,因为它将责任转移到函数的编写者身上,而不是将被警告的用户 linters 通常检测非等待的协程调用而不是 returned awaitables。所以另一种解决方案是使 ws_connect
成为一个普通函数,然后 await ws_connect
中的 await
将应用于 returned 值(=任务),而不是函数本身。