Python asyncio中Future取消后回调未触发及死循环问题咨询
问题分析与解决方案
首先,你的代码里有两个核心问题导致了当前的异常现象:
1. 无限阻塞的check_stats协程
你的check_stats是异步函数,但里面的while循环没有任何await操作——这会让这个协程完全占据事件循环,导致其他任务(包括future的回调函数done_callback)根本没有机会被执行。事件循环被这个死循环卡住,自然无法处理future.cancel()触发的回调,也永远不会执行loop.stop()。
2. 回调执行时机与事件循环调度
当你调用future.cancel()时,虽然future的状态会立刻变为cancelled和done,但add_done_callback注册的回调函数并不是同步执行的——它需要等待事件循环调度执行。而被死循环卡住的事件循环根本无法处理这个回调任务。
修改后的代码
我们需要给check_stats的循环加上await来让出事件循环,同时调整细节确保逻辑符合预期:
import asyncio _future_set = asyncio.Future() def done_callback(f): if f.exception(): _future_set.set_exception(f.exception()) # 注意要调用exception()方法,而非直接引用 elif f.cancelled(): if not _future_set.done(): _future_set.cancel() else: _future_set.set_result(None) async def check_stats(future): while not future.done(): # 检查future是否完成(包含取消状态) print("not done") await asyncio.sleep(0.1) # 让出事件循环,让其他任务有执行机会 loop.stop() def set(future): if not _future_set.done(): future.add_done_callback(done_callback) loop = asyncio.new_event_loop() future = loop.create_future() asyncio.ensure_future(check_stats(future), loop=loop) set(future) future.cancel() loop.run_forever() loop.close() # 事件循环结束后关闭,避免资源泄漏 print(_future_set.cancelled()) # 现在会输出True
关键修改说明
- 给
check_stats添加await:await asyncio.sleep(0.1)会让协程暂停并让出事件循环,这样事件循环就能处理done_callback的执行请求,同时也能在future完成(被取消)时退出循环并调用loop.stop()。 - 修复
f.exception()的调用:原代码里写的是f.exception(仅引用方法),应该改为f.exception()(调用方法获取异常对象),否则会抛出类型错误。 - 在
done_callback里增加状态检查:确保只有当_future_set未完成时才执行取消操作,避免不必要的错误。 - 添加
loop.close():事件循环结束后关闭是Python异步编程的良好实践,避免资源泄漏。
这样修改后,代码就能按照你的预期运行:future.cancel()触发done_callback,_future_set被取消,最终print(_future_set.cancelled())会输出True,事件循环也能正常终止。
内容的提问来源于stack exchange,提问作者Idan Haim Shalom
相关产品推荐
相关产品推荐

