"async with" asyncio.Task 内不工作

"async with" within asyncio.Task not working

我正在学习 python asyncio 并使用它们测试大量代码。
下面是我尝试使用 asyncioaiohttp.

订阅多个 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 中有一些额外的 awaitcoro() 一个开始的机会 - 因此打印 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 值(=任务),而不是函数本身。