介紹
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