为何loop.run_forever()会阻塞主线程?asyncio代码执行疑问
问题分析与解答
首先我们来拆解你的代码行为,看看为什么和预期不符:
1. 旧版协程(@coroutine + yield)的运行逻辑
你用的是Python asyncio早期的协程写法,这种写法的协程行为和你的理解有偏差:
- 当调用
coro()时,得到的是一个生成器协程对象,loop.run_until_complete()的核心职责是持续调度这个协程,直到它彻底完成(生成器抛出StopIteration)。 - 你的
coro()里写了while True无限循环,每次yield只是暂时交出控制权,但协程本身并没有结束——事件循环会立刻把它重新调度回来,继续执行下一次循环的print和counter +=1,永远不会停止。
所以你的代码里,loop.run_until_complete(coro())这一行就已经让事件循环无限运行了,后面的loop.run_forever()根本不会被执行到,程序会一直打印Executed0、Executed1、Executed2...直到你手动终止。
2. 为什么预期结果没出现?
你以为yield交出控制权后,协程就会停止,run_until_complete就会结束,接着执行后续代码——但这是对run_until_complete和旧版协程的误解:
run_until_complete不会在协程暂停时停止,只会在协程彻底结束时才会返回。- 裸
yield在@coroutine装饰的生成器里,只是告诉事件循环“我暂时让出CPU,你可以调度其他任务,但我还没做完,记得再叫我”,而不是“我完成了”。
3. 如何修正代码达到预期?
如果你想让协程只执行一次,然后程序输出Finished!,可以做以下修改:
方法1:让协程执行一次后结束
import asyncio from asyncio.coroutines import coroutine @coroutine def coro(): counter: int = 0 print("Executed" + str(counter)) # 去掉while True,让协程执行一次就终止 yield loop = asyncio.get_event_loop() loop.run_until_complete(coro()) # 事件循环在run_until_complete后会自动停止,无需再调用run_forever() print("Finished!")
方法2:使用现代async/await语法(推荐)
现在Python已经不推荐使用@coroutine和裸yield,更清晰的现代写法是:
import asyncio async def coro(): counter: int = 0 print("Executed" + str(counter)) # 如果需要临时交出控制权,用await asyncio.sleep(0),这里我们只执行一次 # await asyncio.sleep(0) async def main(): await coro() loop = asyncio.get_event_loop() loop.run_until_complete(main()) print("Finished!")
额外说明:关于loop.run_forever()
loop.run_forever()会让事件循环一直运行,直到你主动调用loop.stop()。但它必须在事件循环未运行时调用——如果之前用run_until_complete启动过循环,run_until_complete结束后循环会自动停止,此时再调用run_forever()会重新启动循环并阻塞,直到手动停止。但在你的原代码里,这一步根本没机会执行,因为前面的run_until_complete已经无限运行了。
内容的提问来源于stack exchange,提问作者Notbad




