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
2
3
4
5
using NetworkReply = std::variant<QByteArray,QNetworkReply::NetworkError>;
// 网络请求的结果盒子:要么装“拿到的文件数据(QByteArray)”,要么装“网络错误(Timeout/404等)”
// 写文件的结果盒子:要么装“成功写入的文件路径(QString)”,要么装“IO错误(读失败/写失败)”
enum class IOError{FailedToRead,FailedToWrite}
using IOResult = std::variant<QString,IOError>

使用then()将这两个操作结合起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 开新线程发网络请求,返回“网络结果盒子”
QFuture<IOResult> future = QtConcurrent::run([url] {
// 这里是实际发网络请求的逻辑(比如访问url拿文件)
// 示例里模拟“请求超时”,返回装了“超时错误”的NetworkReply盒子
return NetworkReply(QNetworkReply::TimeoutError);
})
// 2. then():等网络请求做完,自动执行“写文件”操作
.then([](NetworkReply reply) {
// 先检查:网络结果盒子里是不是装了“网络错误”?
//if (auto ptr = std::get_if<类型>(&变体)) 等价于 “检查变体中是否存的是该类型,若是则取出指针”
if (auto error = std::get_if<QNetworkReply::NetworkError>(&reply)) {
// 是错误→返回装了“读失败”的IO结果盒子
return IOResult(IOError::FailedToRead);
}

// 不是错误→从网络盒子里取出“拿到的文件数据”
auto data = std::get_if<QByteArray>(&reply);
//取出指针后必须判空,再解引用使用(否则空指针崩溃);
// 尝试把data写入文件:
// - 写成功→返回装了“文件路径”的IO结果盒子;
// - 写失败→返回装了“写失败”的IO结果盒子
...
});

相比异常的好处:

如果用 try/catch,一旦网络 / 写文件出错,会抛出异常,还要写一堆 catch;用 variant 的话:

  • 错误是 “显式装在盒子里” 的,不用怕漏抓异常;
  • 能精准知道是 “网络超时”“读失败” 还是 “写失败”,方便针对性处理;
  • 代码更清晰,不用嵌套 try/catch,一眼能看到所有错误情况。

链式调用

QtFuture 链式调用里,每个then/onCanceled/onFailed都是 “一步操作”,触发规则就看上一步的状态

上一步状态 优先触发的处理函数 状态传播规则
执行成功(有结果) then() 继续走下一个链式操作
被取消(cancel) onCanceled() 若没有 onCanceled,取消状态会 “传给下一级”
抛出异常(失败) onFailed() 若没有 onFailed,异常状态会 “传给下一级”
例子 1:带 Block2(onCanceled)的链式调用
1
2
testFuture.then(Block1).onCanceled(Block2).onFailed(Block3)
.then(Block4).onFailed(Block5).onCanceled(Block6)
  • 情况 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
2
testFuture.then(Block1).onFailed(Block3)
.then(Block4).onFailed(Block5).onCanceled(Block6)
  • 情况:testFuture 被取消 → 没有 onCanceled 处理 → 取消状态传给下一级 then (Block4) → Block4 也被取消 → 触发 Block6(onCanceled);
  1. 链式调用要按 “then→onFailed→onCanceled” 顺序写,避免状态漏处理;
  2. 取消 / 异常处理完后,若不想继续走后面的 then,可在 onCanceled/onFailed 里返回 “取消状态”。

唯一延续特性

QFuture 和 QPromise 是 “一一绑定” 的:同一个 Promise 的 Future,不管复制多少次,都是同一个内部状态,且then()会 “覆盖” 不是 “追加”!

例子拆解(只打印 second)
1
2
3
4
5
6
7
8
QPromise<int> p;
QFuture<int> f1 = p.future(); // f1绑定p的状态
f1.then([](int) { qDebug("first"); }); // 给f1加第一个延续

QFuture<int> f2 = p.future(); // f2和f1是同一个Future(共享状态)
f2.then([](int) { qDebug("second"); }); // 覆盖了f1的延续

p.start(); p.addResult(42); p.finish(); // 触发延续→只执行second

想给同一个 Future 加多个延续?别直接多次 then,要在一个 then 里包所有逻辑

1
2
3
4
f1.then([](int res) {
qDebug("first");
qDebug("second"); // 一次then里执行多个操作
});

工作窃取特性

1
2
3
4
5
6
7
// 开异步任务
QFuture<int> future = QtConcurrent::run([]() {
return 1+1; // 计算任务
});

// 主线程请求结果(future.result()会阻塞等结果)
int res = future.result();
  • 若线程池的所有工作线程都在忙→ 主线程不会傻等,而是自己执行这个 “1+1” 的计算,拿到结果后再继续;
  • 若线程池有空闲线程→ 还是由工作线程执行,主线程不干活,只等结果。

优点:

  1. 防止死锁,没有这个特性的话,如果工作线程全部都被卡死,主线程还搁那傻傻等结果就会死锁
  2. 优化线程使用,不浪费资源。比如线程池有 3 个线程,其中 2 个闲、1 个忙到爆:
  • 空闲的 2 个线程会 “偷” 忙碌线程的任务来做,所有线程都不摸鱼;
  • 若 3 个线程都忙,而主线程正好闲着等结果→ 主线程也来帮忙做,最大化利用所有可用线程(包括请求结果的线程)。

与运行任务交互

可以使用cancel() 函数取消计算。

要暂停或恢复计算,请使用setSuspended() 函数或suspend(),resume() 或toggleSuspended() 方便函数之一。

请注意,并非所有正在运行的异步计算都可以取消或暂停。例如,QtConcurrent::run() 返回的 future 不能取消;但 QtConcurrent::mappedReduced() 返回的 future 可以。

能取消 / 暂停的场景(以 mappedReduced 为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一步:定义批量处理函数(比如给列表每个数+1)
int addOne(int num) {
return num + 1;
}
// 第二步:定义合并结果的函数
void sumResult(int &total, int num) {
total += num;
}

// 第三步:启动批量任务
QList<int> nums = {1,2,3,4,5};
QFuture<int> future = QtConcurrent::mappedReduced(nums, addOne, sumResult);

// 取消任务(比如用户点了“取消按钮”)
future.cancel();

// 暂停任务(比如用户点了“暂停按钮”)
future.suspend(); // 等价于future.setSuspended(true)
// 恢复任务
future.resume(); // 等价于future.setSuspended(false)
// 切换暂停/恢复状态(点一下暂停,再点一下恢复)
future.toggleSuspended();
不能取消的场景(QtConcurrent::run)
1
2
3
4
5
6
QFuture<void> future = QtConcurrent::run([]() {
// 模拟耗时操作
for (int i=0; i<1000000; i++);
});

future.cancel(); // 无效!线程会继续跑完,cancel()啥也不做
关键注意点
  1. 取消 / 暂停不是 “立刻生效”:mappedReduced 这类任务,会在 “处理完当前一个数据项” 后检查状态,比如处理到第 3 个数时调用 cancel (),会等第 3 个数处理完再停,不会中途打断;
  2. 取消后不能恢复:cancel () 是 “终止任务”,暂停(suspend)是 “临时停”,恢复(resume)后能继续;
  3. 怎么判断任务状态?
1
2
3
4
5
6
if (future.isCanceled()) {
qDebug("任务被取消了");
}
if (future.isSuspended()) {
qDebug("任务暂停了");
}

QFuture进阶(组合,信号绑定,快速就绪future)

一、QtFuture::connect ():把信号 “变成” QFuture(信号也能链式调用)

普通信号槽是 “发信号→触发槽函数”,而QtFuture::connect()能把信号转换成 QFuture 对象,这样信号触发时,就能像处理 Future 一样加then()链式操作,还能控制执行线程~

例子:给按钮点击信号绑链式操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QPushButton *btn = new QPushButton("点我");

// 把按钮的clicked信号转换成QFuture
auto future = QtFuture::connect(btn, &QPushButton::clicked);

// 信号触发时,链式执行操作(支持多步、指定线程)
future
.then([]() { // 第一步:主线程执行(UI操作)
qDebug("按钮被点击了(主线程)");
})
.then(QtFuture::Launch::Async, []() { // 第二步:新线程执行(耗时操作)
qDebug("耗时操作(新线程):%d", QThread::currentThreadId());
})
.then([]() { // 第三步:回到主线程
qDebug("操作完成(主线程)");
});

核心优势:

  1. 信号也能加多个then(),按顺序执行;
  2. QtFuture::Launch::Async指定某步在新线程跑,不用手动创建线程;
  3. 比普通信号槽更灵活,能串起 “UI 操作→耗时操作→UI 更新” 全流程。
二、QtFuture::whenAll ()/whenAny ():组合多个 Future(批量等结果)

当你有多个异步任务(比如同时发 3 个网络请求),可以用这两个函数 “汇总” 结果,不用一个个等

函数 作用(奶奶能懂) 触发时机
whenAll() 所有Future 都完成,才触发后续操作 最后一个 Future 完成时
whenAny() 任意一个Future 完成,就触发后续操作 第一个 Future 完成时

例子 1:whenAll ()(等所有网络请求完成)

1
2
3
4
5
6
7
8
9
10
11
// 3个异步任务(比如3个网络请求)
QFuture<QByteArray> f1 = QtConcurrent::run(getData1);
QFuture<QByteArray> f2 = QtConcurrent::run(getData2);
QFuture<QByteArray> f3 = QtConcurrent::run(getData3);

// 等f1/f2/f3都完成,才执行后续操作
auto allFuture = QtFuture::whenAll(f1, f2, f3);
allFuture.then([](const QList<QByteArray>& results) {
// results[0] = f1的结果,results[1] = f2的结果,results[2] = f3的结果
qDebug("所有请求完成,总数据量:%d", results[0].size() + results[1].size() + results[2].size());
});

例子 2:whenAny ()(只要有一个请求完成就处理)

1
2
3
4
5
6
auto anyFuture = QtFuture::whenAny(f1, f2, f3);
anyFuture.then([](const QtFuture::WhenAnyResult<QByteArray>& result) {
// result.index:完成的Future序号(0=f1,1=f2,2=f3)
// result.value:完成的Future的结果
qDebug("第%d个请求先完成,数据量:%d", result.index, result.value.size());
});

通俗比喻:

  • 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
2
3
4
5
6
7
8
9
10
// 模拟网络请求失败,直接返回带异常的Future
QFuture<QByteArray> mockFailedRequest() {
return QtFuture::makeExceptionalFuture<QByteArray>(std::runtime_error("网络超时"));
}

// 使用这个Future
auto f = mockFailedRequest();
f.onFailed([](const std::runtime_error& e) {
qDebug("任务失败:%s", e.what()); // 打印“网络超时”
});

优势:不用写异步逻辑,就能快速造一个 “成功 / 失败” 的 Future,方便测试、模拟场景

QFutureWatcher

要使用信号和槽与运行中的任务交互,请使用QFutureWatcher

QFutureWatcher 提供有关QFuture 的信息和通知。使用setFuture() 函数开始监视特定的QFuturefuture() 函数返回用setFuture() 设置的Future。

QFuture 本身是 “异步任务的结果对象”,但它没有信号槽(没法通知 UI“任务完成 / 进度更新”);而QFutureWatcher是专门的 “监听器”,把 QFuture 的功能拆成两类:

类别 内容
直接复用的 QFuture 函数 查状态(是否完成 / 取消)、查进度、拿结果等(和 QFuture 用法完全一样)
做成插槽的控制函数 取消 / 暂停 / 恢复任务(能直接绑信号,比如按钮点击触发取消)

1. 直接复用的 QFuture 函数(查状态 / 拿结果)

这些函数和 QFuture 里的用法完全一样,只是挪到了 QFutureWatcher 里,方便通过监听器统一管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QFutureWatcher<int> watcher;
QFuture<int> future = QtConcurrent::mappedReduced(nums, addOne, sumResult);
watcher.setFuture(future); // 绑定Future

// 查状态(和future.isFinished()效果一样)
if (watcher.isFinished()) { qDebug("任务完成"); }
if (watcher.isCanceled()) { qDebug("任务取消"); }

// 查进度(适合批量任务,比如处理了多少数据)
qDebug("当前进度:%d/%d", watcher.progressValue(), watcher.progressMaximum());

// 拿结果(和future.result()效果一样)
int total = watcher.result();

// 等任务完成(阻塞,和future.waitForFinished()一样)
watcher.waitForFinished();

2. 做成插槽的控制函数(绑信号触发)

QFuture 的cancel()/suspend()等是普通函数,而 QFutureWatcher 把它们做成了插槽(slot),能直接绑 UI 信号(比如按钮点击),不用手动写逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
QPushButton *cancelBtn = new QPushButton("取消任务");
QPushButton *pauseBtn = new QPushButton("暂停任务");
QPushButton *resumeBtn = new QPushButton("恢复任务");

QFutureWatcher<int> watcher;
QFuture<int> future = QtConcurrent::mappedReduced(nums, addOne, sumResult);
watcher.setFuture(future);

// 按钮点击 → 触发Watcher的插槽(控制任务)
connect(cancelBtn, &QPushButton::clicked, &watcher, &QFutureWatcher<int>::cancel);
connect(pauseBtn, &QPushButton::clicked, &watcher, &QFutureWatcher<int>::suspend);
connect(resumeBtn, &QPushButton::clicked, &watcher, &QFutureWatcher<int>::resume);
connect(pauseBtn, &QPushButton::clicked, &watcher, &QFutureWatcher<int>::toggleSuspended); // 切换暂停/恢复

3.QFutureWatcher 的核心优势(为什么要用它)

QFuture 本身没有信号,没法通知 UI 更新;而 QFutureWatcher 自带大量信号,比如:

  • finished():任务完成时触发(更新 UI 显示结果);
  • progressValueChanged(int):进度变化时触发(更新进度条);
  • canceled():任务取消时触发(提示用户);

比如:

1
2
3
4
5
6
7
8
9
// 任务完成时,自动更新UI
connect(&watcher, &QFutureWatcher<int>::finished, [&]() {
ui->resultLabel->setText(QString("总结果:%1").arg(watcher.result()));
});

// 进度变化时,更新进度条
connect(&watcher, &QFutureWatcher<int>::progressValueChanged, [&](int val) {
ui->progressBar->setValue(val);
});