Qt程序设计:QPromise/QFuture多线程任务管理

Qt6 在 QFutureInterface 基础上进一步封装出了 QPromise 类,搭配 QFuture 使用,逻辑类似 std::promise + std::future,线程内使用 promise 设置进度和结果,外部通过关联的 future 判断状态和获取结果。

cpp 复制代码
#include <future>

std::promise<T> p;
// 获取与 promise 关联的 future
std::future<T> f = p.get_future(); 

// 线程 A 中设置值
p.set_value(value);
// 线程 B 中阻塞获取 promise 设置的值
T result = f.get();

使用 QFuture 的优势在于,搭配 QFutureWatcher 使用时可以获取 started / finished 等相关信号,这是 QFutureInterface 内部生成的事件被 QFutureWatcher 接收后发出的。

当需要后台执行任务时,界面上一般会有相应的进度提示,借助 QPromise / QFuture 相关接口可以很容易的实现这个功能。在此基础上做一点小小的封装,就得到了一个简易的任务管理类。

封装时一个麻烦的地方是,QFutureWatcher 需要在关联 QFuture 之前关联信号槽,这样我们的 run / start 开始任务接口就需要把所有的槽和槽接收者作为参数。不过这样写起来有点麻烦,就干脆拆成了两部分,先生成 QFutureWatcher,关联好信号槽后再开始任务。

另一个问题是 QPromise / QFuture 的模板类型参数我使用了 QObject*(懒得自定义基类),主要还是考虑到后续进行 QML 版封装用模板不方便,子类化任务返回类型实现起来更顺手一点。

后续扩展:如果要在 QML 中使用,任务接口和返回的 future 对象就需要封装。

TaskManager 实现代码:

cpp 复制代码
#pragma once
#include <QThreadPool>
#include <QFuture>
#include <QFutureWatcher>
#include <QPromise>
#include <functional>
#include <list>

// 任务管理
class TaskManager : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(TaskManager)
private:
    explicit TaskManager(QObject *parent = nullptr);
public:
    ~TaskManager();
    static TaskManager *instance();
    // 任务结果暂时为QObject*,可以用一个自定义基类,包含type判断类型
    using TPromise = QPromise<QObject*>;
    using TWatcher = QFutureWatcher<QObject*>;

    // 创建任务future watcher
    // 如果将槽函数作为run参数传入,代码有点乱,拆分为两步,先创建watcher关联信号槽,再run任务
    auto create() -> std::shared_ptr<TWatcher>;
    // 执行任务,繁忙时任务排队
    void run(const std::function<void (TPromise &)> &task, std::shared_ptr<TWatcher> watcher);
    // 判断是否繁忙
    bool busy() const;
    // 取消所有任务
    void cancel();
    // 等待全部任务结束
    void wait();

signals:
    void taskStarted();
    void taskFinished();

private:
    // 任务线程池
    QThreadPool taskPool;
    // 内部管理任务列表
    std::list<std::shared_ptr<TWatcher>> taskList;
    // 任务列表锁
    std::mutex listMutex;
};

TaskManager::TaskManager(QObject *parent)
    : QObject{parent}
{
    // 线程池最大线程数
    taskPool.setMaxThreadCount(QThread::idealThreadCount());
    // 线程空闲时,销毁判定时间,单位ms,默认半分钟
    taskPool.setExpiryTimeout(1000 * 60);
}

TaskManager::~TaskManager()
{
    cancel();
    wait();
}

TaskManager *TaskManager::instance()
{
    static TaskManager manager;
    return &manager;
}

auto TaskManager::create() -> std::shared_ptr<TWatcher>
{
    return std::make_shared<TWatcher>();
}

void TaskManager::run(const std::function<void (TPromise &)> &task, std::shared_ptr<TWatcher> watcher)
{
    if (!task || !watcher)
        return;

    TPromise promise{};
    auto future = promise.future();

    watcher->setFuture(future);
    {
        // 添加到任务列表
        std::lock_guard<std::mutex> guard(listMutex); Q_UNUSED(guard)
        taskList.push_back(watcher);
        // qDebug() << "push task";
    }

    // 丢进线程池泡澡
    taskPool.start([this, task, watcher, promise = std::move(promise)]() mutable {
        promise.start();
        task(promise);
        promise.finish();
        {
            // 任务结束从列表移除
            std::lock_guard<std::mutex> guard(listMutex); Q_UNUSED(guard)
            taskList.remove(watcher);
            // qDebug() << "remove task";
        }
    });
}

bool TaskManager::busy() const
{
    // 活跃线程数达到上限
    return (taskPool.activeThreadCount() >= taskPool.maxThreadCount());
}

void TaskManager::cancel()
{
    // 移除未启动的任务
    taskPool.clear();
    // 取消所有任务
    std::lock_guard<std::mutex> guard(listMutex); Q_UNUSED(guard)
    for (auto &handle : taskList) {
        handle->cancel();
    }
}

void TaskManager::wait()
{
    // 等待最后一个线程开始
    taskPool.waitForDone(-1);
}

测试代码:

cpp 复制代码
void MainWindow::onRun()
{
    // 先创建future watcher关联信号槽
    auto pwatcher = TaskManager::instance()->create();
    auto watcher = pwatcher.get();

    // 列表更新
    auto table = ui->tableWidget;
    int row = table->rowCount();
    table->insertRow(row);
    QString time = QTime::currentTime().toString("hh:mm:ss.zzz");
    auto title = new QLabel(time);
    table->setCellWidget(row, 0, title);
    auto bar = new QProgressBar();
    table->setCellWidget(row, 1, bar);
    auto btn = new QPushButton("Cancel");
    table->setCellWidget(row, 2, btn);
    connect(btn, &QPushButton::clicked, [pwatcher]{ pwatcher->cancel(); });
    // 测试对象释放
    connect(btn, &QPushButton::destroyed, this, [time]{ qDebug() << "free" << time; });

    // 状态更新
    connect(watcher, &TaskManager::TWatcher::progressTextChanged,
            this, [](const QString &text){
                // qDebug() << "progress text changed" << text;
            });
    connect(watcher, &TaskManager::TWatcher::progressRangeChanged,
            this, [this, bar](int min, int max){
                // qDebug() << "progress range changed" << min << max;
                bar->setRange(min, max);
                bar->setValue(min);
            });
    connect(watcher, &TaskManager::TWatcher::progressValueChanged,
            this, [this, bar](int value){
                // qDebug() << "progress changed" << value;
                bar->setValue(value);
            });
    connect(watcher, &TaskManager::TWatcher::started,
            this, [time](){
                qDebug() << "started" << time;
            });
    connect(watcher, &TaskManager::TWatcher::finished,
            this, [this, table, time, btn, pwatcher](){
                qDebug() << "finished" << time;
                int index = -1;
                for (int row = 0; row < table->rowCount(); ++row) {
                    QPushButton *item = static_cast<QPushButton *>(table->cellWidget(row, 2));
                    if (item && item == btn) {
                        index = row;
                        break;
                    }
                }
                if (index < 0) return;
                ui->tableWidget->removeRow(index);
            });
    connect(watcher, &TaskManager::TWatcher::canceled,
            this, [this, time](){
                qDebug() << "canceled" << time;
            });
    connect(watcher, &TaskManager::TWatcher::resultReadyAt,
            this, [this, time, pwatcher](int index){
                qDebug() << "result" << time << index << pwatcher->resultAt(index);
            });


    // 执行任务,并通过关联的future watcher更新ui
    auto task = [](TaskManager::TPromise &promise){
        promise.setProgressRange(0, 10);
        for (int i = 0; i < 10 && !promise.isCanceled(); ++i)
        {
            promise.setProgressValue(i);
            QThread::sleep(1);
        }
        if (promise.isCanceled())
            return;
        promise.setProgressValue(10);
        promise.addResult(nullptr);
    };
    TaskManager::instance()->run(task, pwatcher);
}

void MainWindow::onCancel()
{
    TaskManager::instance()->cancel();
}

完整代码:https://github.com/gongjianbo/MyTestCode/tree/master/Qt6/TestQt_20251220_Future

相关推荐
无小道2 小时前
Qt——常用控件
开发语言·qt
初次见面我叫泰隆2 小时前
Qt——5、Qt系统相关
开发语言·qt·客户端开发
牵牛老人4 小时前
【Qt 开发后台服务避坑指南:从库存管理系统开发出现的问题来看后台开发常见问题与解决方案】
开发语言·qt·系统架构
xmRao5 小时前
Qt+FFmpeg 实现 PCM 音频转 AAC 编码
qt·ffmpeg·pcm
xmRao5 小时前
Qt+FFmpeg 实现录音程序(pcm转wav)
qt·ffmpeg
喜欢喝果茶.5 小时前
QOverload<参数列表>::of(&函数名)信号槽
开发语言·qt
wjhx6 小时前
QT中对蓝牙权限的申请,整理一下
java·数据库·qt
踏过山河,踏过海6 小时前
【qt-查看对应的依赖的一种方法】
qt·visual studio
C++ 老炮儿的技术栈6 小时前
VS2015 + Qt 实现图形化Hello World(详细步骤)
c语言·开发语言·c++·windows·qt
C++ 老炮儿的技术栈9 小时前
Qt Creator中不写代如何设置 QLabel的颜色
c语言·开发语言·c++·qt·算法