QFuture的使用:多線程與進度條

介紹

QFuture 類可以用來獲取異步計算的結果(類似 std::future ),一般配合 Qt Concurrent 模塊和 QFutureWatcher 類工作。在 Qt Creator 中搜索 concurrent 可以看到一些相關示例。官方示例中, QFuture 一般和 QFutureWatcher 配合,因爲 QFuture 不是 QObject 子類,沒有信號槽。但是 QFuture 相關的接口會觸發 QFutureCallOutEvent 事件,QFutureWatcher 接收該事件後發送對應信號。

相關類的對應基本介紹:

  • QFuture:表示異步計算的結果
  • QFutureWatcher:QFuture本身不帶信號槽,可使用QFutureWatcher進行監控
  • QFutureInterface:該類沒有提供文檔,出現於Qt源碼。QFuture與QFutureInterface的關係類似std::future與std::promise。QFutureInterface可用於生成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<QString>::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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章