当前位置:首页 > 文章列表 > 文章 > python教程 > Python concurrent.futures.wait 怎样区分已完成和未完成任务

Python concurrent.futures.wait 怎样区分已完成和未完成任务

来源:17golang原创 2026-11-02 13:08:52 0浏览 收藏

使用 Python 线程池或进程池时,Future 列表经常不是同时结束的。concurrent.futures.wait 的作用不是按完成顺序逐个产出结果,而是把任务集合切成两个集合:返回时已经完成或已取消的 done,以及仍在运行或尚未开始的 not_done。先看这两个集合,再决定读取结果、取消任务或继续等待,通常比对所有 Future 直接调用 result() 更容易控制。

官方地址:https://docs.python.org/3/library/concurrent.futures.html

要点速览
  • done 包含已完成和已取消的 Future;not_done 包含返回时仍未完成的 Future。
  • return_when 决定何时返回,默认是 ALL_COMPLETED;timeout 只限制本次等待。
  • 读取结果要在 done 集合上逐个调用 result(),并单独捕获任务异常。

先分清 wait 返回的两个集合

wait(fs) 返回一个命名二元组,字段名就是 done 和 not_done。它们是集合而不是按提交顺序排列的列表,因此不要用集合顺序推断任务先后。已取消的 Future 也属于完成状态;只要在 wait 返回的那个时刻已经完成或取消,就会进入 done。

下面的例子故意让任务耗时不同,用 FIRST_COMPLETED 观察“本次等待结束时”的状态划分:

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
import time

def load_part(name, delay):
    # 用不同耗时模拟并发读取,返回值仍然由 Future 保存。
    time.sleep(delay)
    return f"{name}: ok"

with ThreadPoolExecutor(max_workers=3) as executor:
    futures = {
        executor.submit(load_part, "A", 0.1),
        executor.submit(load_part, "B", 0.4),
        executor.submit(load_part, "C", 0.8),
    }
    # FIRST_COMPLETED 表示任意一个 Future 完成或取消后就返回。
    done, not_done = wait(futures, return_when=FIRST_COMPLETED)
    print("已完成数量:", len(done))
    print("未完成数量:", len(not_done))
Python concurrent.futures.wait 将 Future 集合划分为 done 与 not_done 的结构说明图
图1:结构说明图,展示 Future 集合、完成状态与 done/not_done 两个结果集合的静态关系。

这里的 done 可能只有 A,也可能包含多个已经同时完成的任务;not_done 里的 Future 可能处于运行中,也可能还排在执行队列里。想知道某个 Future 是否完成,可以调用它的 done(),但不应该把 not_done 直接当成“永远不会完成”的任务。

return_when 决定等待策略,timeout 决定等待上限

这两个参数解决的是不同问题:return_when 定义“什么事件发生后返回”,timeout 定义“最多等多久”。超时返回时,函数仍然会正常给出两个集合,不会因为有 Future 没完成就自动抛出超时异常。

配置返回时机适合场景
ALL_COMPLETED全部 Future 完成或取消批量任务必须一起汇总
FIRST_COMPLETED任一 Future 完成或取消先拿到一个可用结果
FIRST_EXCEPTION任一 Future 抛出异常;没有异常时等同于全部完成首个失败就进入降级判断

例如,批量任务允许最多等待 0.5 秒,但希望首个异常尽快暴露,可以这样写:

from concurrent.futures import FIRST_EXCEPTION, ThreadPoolExecutor, wait

def run_job(job_id):
    # 真实项目中可在这里调用外部服务,并让异常保留给主线程判断。
    if job_id == "bad":
        raise ValueError("任务参数无效")
    return f"{job_id}: done"

with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(run_job, job_id) for job_id in ("a", "bad", "c")]
    # timeout 只限制 wait 的阻塞时间,不会替 Future 取消任务。
    done, not_done = wait(futures, timeout=0.5, return_when=FIRST_EXCEPTION)
    print("本次返回:", len(done), len(not_done))

如果 0.5 秒到了仍没有任务完成,done 可能为空,全部 Future 都在 not_done 中。不要用“返回了”推断“任务已经成功”,成功与否仍要读取 done 里的结果。

读取 done 结果时要把异常当成结果的一部分处理

wait 只负责状态集合,不会替你读取结果,也不会把任务异常提前抛出。对 done 中的 Future 调用 result() 时,任务内部抛出的异常会在这里重新抛出;已取消的 Future 则会抛出 CancelledError。因此应当逐个处理,而不是让一个异常中断整个汇总循环。

from concurrent.futures import CancelledError

success = []
failures = []
for future in done:
    try:
        # 这里不会再次等待,因为 wait 已经把 Future 放进 done。
        success.append(future.result())
    except CancelledError:
        # 取消也是一种已完成状态,单独记录便于后续补偿。
        failures.append("cancelled")
    except Exception as exc:
        # 只记录当前任务的错误,继续收集其它 Future 的结果。
        failures.append(str(exc))

如果业务需要把任务和业务编号对应起来,可以在提交时建立 future_to_job 映射。不要依赖集合遍历顺序,也不要通过打印顺序判断哪个任务先结束。

not_done 怎么处理:继续等、取消,还是交给下一轮

超时后,not_done 是一个明确的决策入口。正在运行的 Future 通常不能被 cancel();尚未开始执行的 Future 才可能取消成功。因此可以记录未完成任务并在下一轮查询,而不能把 cancel() 的返回值假设为必然成功。

Python concurrent.futures.wait 的等待策略、超时边界、Future 结果和取消关系说明图
图2:职责边界说明图,展示 return_when、timeout、Future 结果处理和取消操作之间的静态关系。
# 超时后只尝试取消仍未开始的任务,并保留取消结果。
cancelled = []
for future in not_done:
    # running 的 Future 会返回 False,说明它仍需等待或由执行器收尾。
    if future.cancel():
        cancelled.append(future)

print("成功取消:", len(cancelled))
print("仍需跟踪:", len(not_done) - len(cancelled))

wait 和 as_completed 的边界也要分清:前者适合一次得到“已完成/未完成”的集合,后者适合按完成顺序迭代 Future。若需求是“任意完成一个就返回并保留剩余任务”,选 FIRST_COMPLETED;若需求是“每完成一个立即消费”,则应考虑 as_completed,不要把两个 API 混成同一种遍历。

常见问题

wait 的 done 集合是按完成顺序排列的吗?

不是。done 是集合,表达的是状态归属,不提供可依赖的完成顺序。需要顺序消费时使用 as_completed。

timeout 到期会自动取消 not_done 吗?

不会。timeout 只让 wait 返回,任务是否继续运行由执行器和任务本身决定;要取消必须显式调用 cancel(),并检查返回值。

FIRST_EXCEPTION 没有异常时什么时候返回?

如果没有 Future 抛出异常,它等同于 ALL_COMPLETED,会等所有 Future 完成或取消。

把 wait 看成“状态分组器”就不容易误用:先根据 return_when 和 timeout 得到当下状态,再逐个处理 done,最后对 not_done 做继续等待或取消决策。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python zipfile.ZipFile.open 怎样按二进制流读取压缩成员Python zipfile.ZipFile.open 怎样按二进制流读取压缩成员
上一篇
Python zipfile.ZipFile.open 怎样按二进制流读取压缩成员
Linux systemd-analyze dot 怎样导出 unit 依赖图
下一篇
Linux systemd-analyze dot 怎样导出 unit 依赖图
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    844次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    844次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    926次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    900次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    730次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码