Qt开发之Future
QFuture
要使用信号和槽与运行中的任务交互,请使用QFutureWatcher 。
详细说明:
QFuture允许一个或者多个结果同步。这写结果将在稍后就绪。
| 场景 | 优先用哪个方法 |
|---|---|
| 归约任务(如统计质数、求和) | result() |
| 多结果按索引取单个值(如指定设备) | resultAt(index) |
| 多结果一次性获取所有值 | results() |
| 多结果实时消费(不等待全部完成) | takeResult() |
如果在调用result(),resultAt(),results() 和takeResult() 函数时结果不可用,QFuture 将等待直到结果可用
可以使用isResultReadyAt() 函数来确定结果是否就绪
对于报告不止一个结果的 QFuture 对象,resultCount() 函数会返回连续结果的数量。这意味着从 0 到resultCount() 的结果遍历始终是安全的。
takeResult()会使一个 future 失效,随后任何访问 result 或 future 中结果的尝试都会导致未定义的行为。isValid() 会告诉你是否可以访问结果。
异步结果传递
解决问题需要将一个异步计算的结果传递给另一个异步计算。
解决办法提供了.then()这种顺序链接多个计算的方法。
onCancel()可以用于添加一个处理程序,以便在QFuture被取消事调用
onFailed用于处理链中出现的任何故障。
需要注意的是QFuture依赖异常来处理错误,如果不能选择使用异常,也可以通过将错误类型作为 QFuture 类型的一部分来指示 QFuture 的错误状态。例如,你可以使用 std::variant、std::any 或类似类型来保存结果或失败,或者创建你的自定义类型。
异常处理示例(不用异常)
核心是用std::variant(二选一容器)存「成功结果」或「错误类型」,用then()把两个操作串起来,全程不用 try/catch 抛异常
假设要处理一个网络请求,从网络位置获取一个文件,然后将其写入文件系统,并在成功时返回其位置。这两项操作都可能因不同的错误失败,因此使用std::variant来保存结果或者错误。
1 | using NetworkReply = std::variant<QByteArray,QNetworkReply::NetworkError>; |
使用then()将这两个操作结合起来
1 | // 1. 开新线程发网络请求,返回“网络结果盒子” |
相比异常的好处:
如果用 try/catch,一旦网络 / 写文件出错,会抛出异常,还要写一堆 catch;用 variant 的话:
- 错误是 “显式装在盒子里” 的,不用怕漏抓异常;
- 能精准知道是 “网络超时”“读失败” 还是 “写失败”,方便针对性处理;
- 代码更清晰,不用嵌套 try/catch,一眼能看到所有错误情况。
链式调用
QtFuture 链式调用里,每个then/onCanceled/onFailed都是 “一步操作”,触发规则就看上一步的状态:
| 上一步状态 | 优先触发的处理函数 | 状态传播规则 |
|---|---|---|
| 执行成功(有结果) | then() |
继续走下一个链式操作 |
| 被取消(cancel) | onCanceled() |
若没有 onCanceled,取消状态会 “传给下一级” |
| 抛出异常(失败) | onFailed() |
若没有 onFailed,异常状态会 “传给下一级” |
例子 1:带 Block2(onCanceled)的链式调用
1 | testFuture.then(Block1).onCanceled(Block2).onFailed(Block3) |
- 情况 1:testFuture 成功 → 触发 Block1 → Block1 成功 → 触发 Block4(继续走 then);
- 情况 2:testFuture 被取消 → 触发 Block2(onCanceled)→ 若 Block2 抛异常 → 触发 Block3(onFailed);
- 情况 3:testFuture 抛异常 → 触发 Block3(onFailed)→ 之后走 Block4;
👉 关键:onCanceled/onFailed是 “针对性处理”,处理完状态就清了,会继续走后面的 then;若没处理(比如删了 onCanceled),状态会 “往下传”。
例子 2:删了 Block2(onCanceled)的链式调用
1 | testFuture.then(Block1).onFailed(Block3) |
- 情况:testFuture 被取消 → 没有 onCanceled 处理 → 取消状态传给下一级 then (Block4) → Block4 也被取消 → 触发 Block6(onCanceled);
- 链式调用要按 “then→onFailed→onCanceled” 顺序写,避免状态漏处理;
- 取消 / 异常处理完后,若不想继续走后面的 then,可在 onCanceled/onFailed 里返回 “取消状态”。
唯一延续特性
QFuture 和 QPromise 是 “一一绑定” 的:同一个 Promise 的 Future,不管复制多少次,都是同一个内部状态,且then()会 “覆盖” 不是 “追加”!
例子拆解(只打印 second)
1 | QPromise<int> p; |
想给同一个 Future 加多个延续?别直接多次 then,要在一个 then 里包所有逻辑
1 | f1.then([](int res) { |
工作窃取特性
1 | // 开异步任务 |
- 若线程池的所有工作线程都在忙→ 主线程不会傻等,而是自己执行这个 “1+1” 的计算,拿到结果后再继续;
- 若线程池有空闲线程→ 还是由工作线程执行,主线程不干活,只等结果。
优点:
- 防止死锁,没有这个特性的话,如果工作线程全部都被卡死,主线程还搁那傻傻等结果就会死锁
- 优化线程使用,不浪费资源。比如线程池有 3 个线程,其中 2 个闲、1 个忙到爆:
- 空闲的 2 个线程会 “偷” 忙碌线程的任务来做,所有线程都不摸鱼;
- 若 3 个线程都忙,而主线程正好闲着等结果→ 主线程也来帮忙做,最大化利用所有可用线程(包括请求结果的线程)。
与运行任务交互
可以使用cancel() 函数取消计算。
要暂停或恢复计算,请使用setSuspended() 函数或suspend(),resume() 或toggleSuspended() 方便函数之一。
请注意,并非所有正在运行的异步计算都可以取消或暂停。例如,QtConcurrent::run() 返回的 future 不能取消;但 QtConcurrent::mappedReduced() 返回的 future 可以。
能取消 / 暂停的场景(以 mappedReduced 为例)
1 | // 第一步:定义批量处理函数(比如给列表每个数+1) |
不能取消的场景(QtConcurrent::run)
1 | QFuture<void> future = QtConcurrent::run([]() { |
关键注意点
- 取消 / 暂停不是 “立刻生效”:mappedReduced 这类任务,会在 “处理完当前一个数据项” 后检查状态,比如处理到第 3 个数时调用 cancel (),会等第 3 个数处理完再停,不会中途打断;
- 取消后不能恢复:cancel () 是 “终止任务”,暂停(suspend)是 “临时停”,恢复(resume)后能继续;
- 怎么判断任务状态?
1 | if (future.isCanceled()) { |
QFuture进阶(组合,信号绑定,快速就绪future)
一、QtFuture::connect ():把信号 “变成” QFuture(信号也能链式调用)
普通信号槽是 “发信号→触发槽函数”,而QtFuture::connect()能把信号转换成 QFuture 对象,这样信号触发时,就能像处理 Future 一样加then()链式操作,还能控制执行线程~
例子:给按钮点击信号绑链式操作
1 | QPushButton *btn = new QPushButton("点我"); |
核心优势:
- 信号也能加多个
then(),按顺序执行; - 用
QtFuture::Launch::Async指定某步在新线程跑,不用手动创建线程; - 比普通信号槽更灵活,能串起 “UI 操作→耗时操作→UI 更新” 全流程。
二、QtFuture::whenAll ()/whenAny ():组合多个 Future(批量等结果)
当你有多个异步任务(比如同时发 3 个网络请求),可以用这两个函数 “汇总” 结果,不用一个个等
| 函数 | 作用(奶奶能懂) | 触发时机 |
|---|---|---|
| whenAll() | 等所有Future 都完成,才触发后续操作 | 最后一个 Future 完成时 |
| whenAny() | 等任意一个Future 完成,就触发后续操作 | 第一个 Future 完成时 |
例子 1:whenAll ()(等所有网络请求完成)
1 | // 3个异步任务(比如3个网络请求) |
例子 2:whenAny ()(只要有一个请求完成就处理)
1 | auto anyFuture = QtFuture::whenAny(f1, f2, f3); |
通俗比喻:
- whenAll ():等 3 个快递都到了,才一起拆;
- whenAny ():哪个快递先到,先拆哪个。
三、便捷创建 “就绪状态” 的 QFuture(不用等,直接有结果 / 异常)
有时候不需要异步执行,只想快速创建一个 “已经完成” 的 Future(比如模拟成功 / 失败结果),就用这些便捷函数:
| 函数 | 作用 | 例子 |
|---|---|---|
| makeReadyVoidFuture() | 创建 “无返回值、就绪” 的 Future | auto f = QtFuture::makeReadyVoidFuture(); |
| makeReadyValueFuture (值) | 创建 “带指定值、就绪” 的 Future | auto f = QtFuture::makeReadyValueFuture(42);(值为 42) |
| makeReadyRangeFuture (范围) | 创建 “带数值范围、就绪” 的 Future(比如 1-10) | auto f = QtFuture::makeReadyRangeFuture(1, 10); |
| makeExceptionalFuture (异常) | 创建 “带异常、就绪” 的 Future(模拟失败) | auto f = QtFuture::makeExceptionalFuture(std::runtime_error("请求失败")); |
例子:模拟异步任务失败(返回异常 Future)
1 | // 模拟网络请求失败,直接返回带异常的Future |
优势:不用写异步逻辑,就能快速造一个 “成功 / 失败” 的 Future,方便测试、模拟场景。
QFutureWatcher
要使用信号和槽与运行中的任务交互,请使用QFutureWatcher 。
QFutureWatcher 提供有关QFuture 的信息和通知。使用setFuture() 函数开始监视特定的QFuture 。future() 函数返回用setFuture() 设置的Future。
QFuture 本身是 “异步任务的结果对象”,但它没有信号槽(没法通知 UI“任务完成 / 进度更新”);而QFutureWatcher是专门的 “监听器”,把 QFuture 的功能拆成两类:
| 类别 | 内容 |
|---|---|
| 直接复用的 QFuture 函数 | 查状态(是否完成 / 取消)、查进度、拿结果等(和 QFuture 用法完全一样) |
| 做成插槽的控制函数 | 取消 / 暂停 / 恢复任务(能直接绑信号,比如按钮点击触发取消) |
1. 直接复用的 QFuture 函数(查状态 / 拿结果)
这些函数和 QFuture 里的用法完全一样,只是挪到了 QFutureWatcher 里,方便通过监听器统一管理:
1 | QFutureWatcher<int> watcher; |
2. 做成插槽的控制函数(绑信号触发)
QFuture 的cancel()/suspend()等是普通函数,而 QFutureWatcher 把它们做成了插槽(slot),能直接绑 UI 信号(比如按钮点击),不用手动写逻辑
1 | QPushButton *cancelBtn = new QPushButton("取消任务"); |
3.QFutureWatcher 的核心优势(为什么要用它)
QFuture 本身没有信号,没法通知 UI 更新;而 QFutureWatcher 自带大量信号,比如:
finished():任务完成时触发(更新 UI 显示结果);progressValueChanged(int):进度变化时触发(更新进度条);canceled():任务取消时触发(提示用户);
比如:
1 | // 任务完成时,自动更新UI |



