add_done_callback不触发的根本原因是future未提交到事件循环,常见于直接调用协程而非创建task;回调须为同步函数,用future.result()安全取值,按注册顺序在事件循环线程中执行。

add_done_callback 为什么经常不触发
根本原因不是函数写错了,而是 Future 对象没被真正提交到事件循环中执行。常见于直接调用协程函数却没用 asyncio.create_task() 或 await,导致返回的是一个未调度的协程对象,调用 add_done_callback() 时绑定的是“假 Future”。
实操建议:
- 务必确保传给
add_done_callback()的是真正的asyncio.Task或已调度的Future,而不是裸协程 - 检查是否误写了
my_coro()(调用)而非my_coro(引用)——后者会报TypeError: object is not callable - 在主线程中启动事件循环后,必须显式
run_until_complete()或run_forever(),否则回调永远不会运行
回调函数里如何安全获取返回值和异常
add_done_callback() 传入的回调只接收一个参数:完成的 Future 实例。不能直接 return,也不能 await,必须用 future.result() 或 future.exception() 显式提取结果。
实操建议:
- 始终用
try/except包裹future.result(),因为成功时返回值,失败时抛出异常 - 不要在回调里再起新协程(如
asyncio.create_task(...))却不 await —— 这会导致任务被丢弃且无日志 - 若需异步处理结果,应在回调中调用
asyncio.create_task(handle_result(future)),其中handle_result是完整协程函数
def on_task_done(future):
try:
result = future.result() # ✅ 安全取值
print("Done:", result)
except Exception as e:
print("Failed:", e)
多个回调的执行顺序和线程安全性
add_done_callback() 添加的回调按注册顺序同步执行,全部在事件循环线程中运行(非新线程),所以天然线程安全,但要注意阻塞风险。
实操建议:
- 回调函数必须是普通同步函数,不能是
async def—— 否则会被静默忽略(不会报错,也不会执行) - 避免在回调里做耗时操作(如文件读写、HTTP 请求),否则会阻塞整个事件循环;应转为
asyncio.to_thread()或交由线程池 - 同一个
Future上多次调用add_done_callback(),所有回调都会执行,无去重逻辑
对比 await 和 add_done_callback 的适用场景
二者不是替代关系,而是协作关系:await 适合主流程等待,add_done_callback 适合“发射后不管”的监听,比如后台任务完成通知、资源清理、日志埋点等。
实操建议:
- 需要按顺序等待多个异步操作?用
await asyncio.gather(t1, t2),别用回调链 - 想在任务结束时发 Slack 通知、更新数据库状态、关闭临时文件句柄?用
add_done_callback更解耦 - 回调里无法
await,所以不能直接 await 另一个协程;若需 await,必须包装成asyncio.create_task()并确保其被事件循环驱动
最易被忽略的一点:回调函数执行时,原协程可能早已退出作用域,闭包变量若依赖局部状态(如循环变量 i),容易捕获到错误的值 —— 建议显式传参或用默认参数固化。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











