Python concurrent.futures.wait 怎样区分已完成和未完成任务
来源:17golang原创
时间:2026-11-02 13:08:52 276浏览 收藏
使用 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))

这里的 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() 的返回值假设为必然成功。

# 超时后只尝试取消仍未开始的任务,并保留取消结果。
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 做继续等待或取消决策。
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习