【QT】QFuture的使用:多线程与进度条

9 篇文章 1 订阅

【QT】QFuture的使用:多线程与进度条

介绍

QFuture 类可以用来获取异步计算的结果类似 std::future ),一般配合 Qt Concurrent 模块QFutureWatcher 类工作。在 Qt Creator 中搜索 concurrent 可以看到一些相关示例。官方示例中, QFuture 一般和 QFutureWatcher 配合,因为 QFuture 不是 QObject 子类,没有信号槽。但是 QFuture 相关的接口会触发 QFutureCallOutEvent 事件,QFutureWatcher 接收该事件后发送对应信号。

相关类的对应基本介绍:

  • QFuture表示异步计算的结果

  • QFutureWatcherQFuture本身不带信号槽,可使用QFutureWatcher进行监控

  • QFutureInterface:该类没有提供文档,出现于Qt源码。QFuture与QFutureInterface的关系类似std::future与std::promiseQFutureInterface可用于生成QFuture,在Qt类实现中,QFuture持有一个QFutureInterface成员。

从目前的文档和示例来看,QFuture 主要是由 Qt Concurrent 的相关接口生成,一般操作如下:

//concurrent生成future
QFuture<void> future = QtConcurrent::run([this]{ /**/ });
 
//future设置给watcher进行监控
QFutureWatcher<void> theWatcher;
theWatcher.setFuture(future);
 
//future对应的操作结束,触发watcher信号
connect(&theWatcher,&QFutureWatcher<void>::finished,this,[this]{ /**/ });

本文的需求是实现一个展示多线程处理进度的进度条,但 QFuture 只有获取状态值的接口,而没有设置状态值的接口,这些都被封装在了 QFutureInterface 中。要实现需求,需要创建一个 QFutureInterface 对象,并用他来生成一个相关联的 QFuture 对象,再设置给 QFutureWatcher 进行监测,在线程中我们操作 QFutureInterface 实例就能触发 QFutureWatcher 对应的信号。


功能实现

实现效果(GIF):
在这里插入图片描述
完整代码链接(GitHub):
https://github.com/gongjianbo/MyTestCode/tree/master/Qt/TestQt_20200625_QFuture

主要实现代码(组件是在 ui 上拖的):

(其实这里遇到一个问题,就是调用 cancel 来取消的话,会异常结束或者卡死,所以暂时用的 pause 来结束,线程中判断是否 pause。还要注意一个问题是,pause 或者 cancel 后,后续的设置是不会触发信号的,所以我在线程中结束了pause。)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
//QFuture类表示异步计算的结果
#include <QFuture>
//QFuture本身不带信号槽,可使用QFutureWatcher进行监控
#include <QFutureWatcher>
//QFutureInterface没有提供文档,出现于Qt源码
//QFuture与QFutureInterface的关系类似std::future与std::promise
//QFutureInterface可用于生成QFuture
//在Qt类实现中,QFuture持有一个QFutureInterface成员
#include <QFutureInterface>
 
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
 
private:
    Ui::MainWindow *ui;
    //QFuture本身不带信号槽,可使用QFutureWatcher进行监控
    QFutureWatcher<bool> myWatcher;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
 
#include <thread>
 
#include <QThread>
#include <QDebug>
 
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    connect(ui->btnStart,&QPushButton::clicked,[this](){
        qDebug()<<"点击 start"<<QThread::currentThread();
        if(!myWatcher.isFinished()){
            qDebug()<<"老哥,当前任务还没结束,等等呗";
            return;
        }
        QFutureInterface<bool> interface;
        interface.reportStarted();
        interface.setProgressRange(0,100);
        myWatcher.setFuture(interface.future());
        //QFutureInterface是允许拷贝的,里面有一个d指针
        std::thread thread([this,interface]() mutable {
            qDebug()<<"线程开始"<<QThread::currentThread();
            //使用sleep模拟耗时处理过程
            bool result=true;
            for(int i=0;i<10;i++)
            {
                //操作前判断是否结束
                if(interface.isPaused()){
                    //pause后不接收值了
                    //interface.setProgressValueAndText(0,"Cancel");
                    result=false;
                    //不恢复watcher不能收到消息
                    interface.setPaused(false);
                    break;
                }
 
                //使用sleep模拟耗时处理过程
                QThread::msleep(200);
 
                //设置值前判断是否结束
                if(interface.isPaused()){
                    result=false;
                    interface.setPaused(false);
                    break;
                }
                const int progress=(i+1)*10;
                interface.setProgressValueAndText(
                            progress,
                            QString("已处理 %%1").arg(progress));
            }
            interface.reportResult(result);
            interface.reportFinished();
            qDebug()<<"线程结束"<<QThread::currentThread();
        });
        thread.detach(); //分离不管他了,用future来确保退出
    });
    connect(ui->btnCancel,&QPushButton::clicked,[this](){
        qDebug()<<"点击 cancel"<<QThread::currentThread();
        //因为调用cancel会异常,所以我先拿pause来实现结束标志位的设置,会在线程中判断该标志
        //线程中的分段任务完成才会退出
        myWatcher.pause();
    });
 
    //异步结果
    connect(&myWatcher,&QFutureWatcher<bool>::finished,[=]{
        qDebug()<<"任务已完成"<<myWatcher.result();
        if(myWatcher.result()){
            ui->label->setText("任务完成");
        }else{
            ui->label->setText("任务失败");
        }
    });
    connect(&myWatcher,&QFutureWatcher<bool>::paused,[=]{
        qDebug()<<"任务已取消";
    });
 
    //进度条相关
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);
    connect(&myWatcher,&QFutureWatcher<bool>::progressValueChanged,[=](int value){
        qDebug()<<"Value"<<value<<QThread::currentThread();
        ui->progressBar->setValue(value);
    });
    connect(&myWatcher,&QFutureWatcher<bool>::progressTextChanged,[=](const QString text){
        qDebug()<<"Text"<<text<<QThread::currentThread();
        ui->label->setText(text);
    });
}
 
MainWindow::~MainWindow()
{
    myWatcher.pause();
    myWatcher.waitForFinished();
    delete ui;
}
 

参考

文档:https://doc.qt.io/qt-5/qfuture.html

博客:https://zhuanlan.zhihu.com/p/81566592?from_voters_page=true

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值