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

相关推荐
追烽少年x2 小时前
Qt面试题合集(三)
qt
FirstFrost --sy2 小时前
Qt控件美化:LineEdit与CheckBox实战
开发语言·qt
Larry_Yanan11 小时前
Qt多进程(一)进程间通信概括
开发语言·c++·qt·学习
小灰灰搞电子15 小时前
Qt 开发环境选择Qt Creator、Visual Studio还是 VS Code?
开发语言·qt·visual studio
淼淼76317 小时前
Qt调度 程序
开发语言·c++·windows·qt
明飞198718 小时前
QT笔记1
qt
林政硕(Cohen0415)18 小时前
ARM Qt 字体过小的问题
arm开发·qt
追烽少年x18 小时前
Qt中构建多语言程序
qt