当前位置:首页 > 文章列表 > 文章 > python教程 > Python Future.add_done_callback 怎样集中处理线程任务结果

Python Future.add_done_callback 怎样集中处理线程任务结果

来源:17golang原创 2026-10-17 15:43:37 0浏览 收藏

批量提交线程池任务后,很多代码只保存了 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 已经完成,回调会立即被调用;因此回调函数不能假设“注册之后才会触发”,也不要在里面依赖某个尚未初始化的外部状态。

Python Future 从提交到完成回调的静态生命周期说明图
图1:结构说明图,展示任务提交、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() 的提交顺序;如果业务要求按输入顺序输出,就要额外保存序号。

Python ThreadPoolExecutor 用完成回调汇总任务结果的静态关系图
图2:结构说明图,展示 ThreadPoolExecutor、带序号的 Future、完成回调、线程安全队列和主线程汇总之间的关系;图中不表示实际执行证据。
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 的对应关系,主线程汇总后按序号排序即可。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python functools.partial 固定部分参数的回调函数Python functools.partial 固定部分参数的回调函数
上一篇
Python functools.partial 固定部分参数的回调函数
Go ServeMux 怎样组织带方法和路径参数的路由
下一篇
Go ServeMux 怎样组织带方法和路径参数的路由
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    505次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    563次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    590次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    542次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    353次使用