为何启动后台任务后Asyncio Generator会永久挂起?
解决Asyncio生成器永久挂起的问题
嘿,我来帮你拆解这个异步生成器挂起的问题!你想要的是让HTTP调用和do_work并行执行,先返回do_work的结果,再拿HTTP调用的结果,但代码却卡住了对吧?核心问题出在HTTP调用的协程没有被正确调度执行,或者生成器的执行流程和异步任务的生命周期没配合好。
先看你的错误场景(典型写法)
大概率你的代码是类似这样的:
async def problematic_generator(): # 只创建了HTTP协程对象,但没交给事件循环调度 http_coro = do_http_call() # 先执行并返回do_work的结果 work_result = await do_work() yield work_result # 这时候才开始执行HTTP协程,直接导致生成器在这里挂起等待 http_result = await http_coro yield http_result
为啥会挂起?因为do_http_call()只是生成了一个协程对象,没有被事件循环加入调度队列——只有当你await它的时候,它才会真正开始执行。而你先去处理do_work并返回结果,这时候HTTP调用根本没启动,等你走到await http_coro时才刚开始跑,如果do_http_call是个耗时操作,生成器就会一直卡在这儿。
正确的并行实现方案
要实现你想要的“后台跑HTTP,先返回do_work结果,再拿HTTP结果”的需求,得用asyncio.create_task()把HTTP调用包装成后台任务,让事件循环在处理do_work的间隙同时调度它执行:
import asyncio async def working_generator(): # 启动HTTP任务,直接交给事件循环后台运行 http_task = asyncio.create_task(do_http_call()) # 执行do_work的同时,HTTP调用已经在后台跑起来了 work_result = await do_work() # 先返回do_work的结果 yield work_result # 等待已经在后台执行的HTTP任务完成,拿到结果 try: http_result = await http_task yield http_result finally: # 保险起见,确保任务被清理,避免资源泄漏 if not http_task.done(): http_task.cancel()
为啥这样能解决问题?
asyncio.create_task()会立刻把do_http_call扔进事件循环的调度队列,在await do_work()的过程中,事件循环会在do_work的IO等待间隙(比如网络IO、sleep)去调度HTTP任务执行,完全不会浪费时间。- 当你yield
work_result后,生成器会挂起,直到你再次迭代它(比如调用await anext(generator)),这时候去await http_task,其实是在等一个已经跑了一段时间的任务,不会出现“协程还没启动”的情况,自然也就不会永久挂起了。
为啥“启动和获取都放yield之后”能正常运行?
这种写法是串行执行:先做完do_work返回结果,再启动并等待HTTP调用,整个流程是线性的,事件循环可以按顺序逐个调度协程,不会出现任务没被调度的情况,所以不会挂起。但缺点是没用到异步的并行能力——HTTP调用要等do_work完全做完才开始,白白浪费了并行执行的时间。
内容的提问来源于stack exchange,提问作者stevenheidel
相关产品推荐
相关产品推荐

