Python Future.add_done_callback 怎样集中处理线程任务结果
批量提交线程池任务后,很多代码只保存了 Future,却没有统一处理任务完成、取消和异常。add_done_callback() 适合把这段收尾逻辑集中起来:任务结束时回调自动收到对应的 Future,再由回调读取状态并写入结果记录。
官方地址:https://docs.python.org/3/library/concurrent.futures.html
add_done_callback() 只负责在 Future 完成或取消后通知回调;它不会改变任务调度顺序,也不会替你把异常重新抛回提交线程。回调中应先判断取消,再读取异常或结果。
- 回调函数只接收一个 Future 参数,结果、异常和取消状态都从它读取。
- 调用
result()会重新抛出任务异常,调用exception()会返回异常对象。 - 完成回调按任务完成时机触发,批量汇总应使用线程安全容器或把结果交回主线程。
先看懂 add_done_callback 的触发边界
官方文档规定,add_done_callback(fn) 会在 Future 被取消或执行结束后调用 fn(future)。如果注册回调时 Future 已经完成,回调会立即被调用;因此回调函数不能假设“注册之后才会触发”,也不要在里面依赖某个尚未初始化的外部状态。

回调收到的不是任务返回值,而是 Future 对象。这样设计是为了让同一套回调同时处理成功、异常和取消三种结果。最小的读取顺序可以写成下面这样:
from concurrent.futures import Future
def report_done(future: Future) -> None:
# 取消状态要先判断,否则 exception() 可能再次抛出取消异常。
if future.cancelled():
print("任务已取消")
return
error = future.exception()
if error is not None:
# exception() 返回任务异常对象,不会在这里自动重新抛出。
print(f"任务失败:{error}")
return
# 没有异常时再读取结果,避免把失败任务当成成功处理。
print(f"任务完成:{future.result()}")
用一个回调统一成功、取消和异常
实际批量处理中,回调通常不应该只打印文本,而应产生结构化记录。下面的例子用线程安全的 queue.Queue 收集完成信息,主线程最后再按记录类型汇总。这样回调只做很短的状态读取和入队,不在工作线程中执行复杂业务。
from concurrent.futures import Future
from queue import Queue
events: Queue[dict] = Queue()
def collect_done(future: Future) -> None:
# 先记录取消,避免对已取消 Future 调用 result() 或 exception()。
if future.cancelled():
events.put({"status": "cancelled"})
return
error = future.exception()
if error is not None:
# 把异常对象转成记录,主线程可以统一决定是否重试。
events.put({"status": "failed", "error": repr(error)})
return
# 成功结果只在确认没有异常后读取。
events.put({"status": "ok", "value": future.result()})
这里没有把回调中的异常继续向提交方抛出。若回调自身抛出 Exception,官方文档规定该异常会被记录并忽略,所以回调内部也应保持简单,并在需要时自行捕获记录错误。
把回调接到线程池,并区分提交顺序与完成顺序
为每个 Future 注册同一个回调即可形成统一收口。任务完成快慢取决于输入和线程调度,因此事件队列里的顺序是完成顺序,不是 submit() 的提交顺序;如果业务要求按输入顺序输出,就要额外保存序号。

from concurrent.futures import ThreadPoolExecutor
from queue import Queue
def work(item: int) -> int:
# 这里代表一个可能成功或失败的阻塞任务。
if item == 3:
raise ValueError("演示任务失败")
return item * 10
events: Queue[dict] = Queue()
def collect(future) -> None:
# 回调只负责收口;完成顺序不等于提交顺序。
if future.cancelled():
events.put({"status": "cancelled"})
return
error = future.exception()
if error is not None:
events.put({"status": "failed", "error": repr(error)})
return
events.put({"status": "ok", "value": future.result()})
with ThreadPoolExecutor(max_workers=3) as executor:
# 用字典保留输入序号,后续需要原序输出时可以按 key 排序。
futures = {}
for index in range(1, 6):
future = executor.submit(work, index)
futures[index] = future
future.add_done_callback(collect)
# 主线程在退出线程池后读取已完成记录,再决定重试或展示结果。
while not events.empty():
print(events.get())
回调线程、共享状态和最终汇总怎么安排
回调函数会在创建它的进程所属线程中被调用,但不要把它当成固定的主线程回调。若回调要更新共享列表、计数器或日志缓冲区,应选择线程安全容器,或者只把小记录放入队列,让主线程统一处理。不要在回调里再次等待同一个线程池中依赖后续任务的结果,否则容易让线程池出现互相等待。
如果需要严格保持输入顺序,可将序号与 Future 对应保存,主线程取完记录后按序号排序;如果更关心哪个任务先完成,则直接消费队列即可。两种需求不要混用,否则读者会误以为回调天然提供有序结果。
| 现象 | 实际含义 | 推荐做法 |
|---|---|---|
| 回调没有返回值 | 返回值不会成为 Future 的任务结果 | 写入队列或显式保存记录 |
| 调用 result() 抛异常 | 任务本身失败 | 先 cancelled,再用 exception() 或捕获 result() |
| 回调顺序变化 | 任务完成时间不同 | 需要原序时保存输入序号 |
| 回调异常不明显 | 回调异常可能被记录并忽略 | 回调内部捕获并写入诊断记录 |
常见问题
add_done_callback 会阻塞提交线程吗?
注册动作本身很短;回调真正执行时可能占用所属进程中的线程。回调应保持轻量,耗时工作继续提交给明确的执行器。
怎样拿到任务异常而不让回调抛出?
先判断 cancelled(),再调用 exception() 取得异常对象,最后把异常转换成日志或结构化记录。
怎样让结果保持提交顺序?
Future 完成回调不保证全局提交顺序。保存输入序号与 Future 的对应关系,主线程汇总后按序号排序即可。
Python functools.partial 固定部分参数的回调函数
- 上一篇
- Python functools.partial 固定部分参数的回调函数
- 下一篇
- Go ServeMux 怎样组织带方法和路径参数的路由
-
- 文章 · python教程 | 1小时前 | 标准库 · Python教程 · Python 回调函数 高阶函数 functools.partial 固定参数
- Python functools.partial 固定部分参数的回调函数
- 373浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python pathlib 组织跨平台文件复制
- 342浏览 收藏
-
- 文章 · python教程 | 1小时前 | 超时控制 · 异步编程 · Python教程 · asyncio · CancelledError 异步超时 TimeoutError reschedule Python asyncio.timeout
- Python asyncio.timeout 控制异步操作时限
- 299浏览 收藏
-
- 文章 · python教程 | 1小时前 | 线程池 · 异步编程 · Python教程 · Python contextvars ContextVar asyncio.to_thread 线程上下文
- Python asyncio.to_thread 传递上下文变量的边界
- 489浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Python wheel 构建元数据和依赖声明
- 182浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Python 正则输入规模与超时防护的边界
- 487浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Python 多进程 Queue 传输结果的关闭方式
- 338浏览 收藏
-
- 文章 · python教程 | 9小时前 | python · 数据分析 · Python 分位数 P95 p99 statistics.quantiles
- Python statistics.quantiles 计算 P95 时的 n 参数与方法选择
- 225浏览 收藏
-
- 文章 · python教程 | 2天前 |
- Python logging lastResort 怎样处理没有配置 handler 的日志
- 289浏览 收藏
-
- 文章 · python教程 | 2天前 | 并发编程 · Python教程 · Python logging 后台线程 logging.handlers.QueueListener 日志关闭
- Python logging.handlers.QueueListener 怎样优雅停止后台日志线程
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 505次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 563次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 590次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 542次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 353次使用
-
- 物流异常件转派时如何保留原单号与处理时限
- 2026-09-20 276浏览
-
- Golang WorkerPool线程池并发模式示例详解
- 2022-12-28 170浏览
-
- Golang异常处理之defer,panic,recover的使用详解
- 2023-01-07 339浏览
-
- SingleFlight模式的Go并发编程学习
- 2023-01-01 285浏览
-
- Go并发编程之sync.Once使用实例详解
- 2022-12-27 484浏览
