登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

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))
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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>