Qt線程QtConcurrent模塊的使用
前言
在傳統(tǒng) Qt 多線程開發(fā)中,我們常通過繼承 QThread 或使用 moveToThread 來實現(xiàn)后臺任務(wù)。但這種方式需要手動管理線程生命周期、信號槽連接、對象所有權(quán)等,樣板代碼多、易出錯。
而 Qt Concurrent 提供了一種更高層次的抽象:你只關(guān)心“做什么”,不關(guān)心“在哪做”。它基于線程池自動調(diào)度任務(wù),極大簡化了并發(fā)編程。
QtConcurrent是Qt特有的一個用于實現(xiàn)并發(fā)任務(wù)的模塊,提供了一組基于任務(wù)(task-based)而非線程(thread-based)的并發(fā)API。它底層使用 QThreadPool + QRunnable 實現(xiàn),自動管理線程池,你無需手動創(chuàng)建/銷毀線程。如果有一個或多個短期簡單任務(wù)會造成主線程的阻塞,又覺得為了他單獨設(shè)計一個任務(wù)類再移動到子線程太過繁瑣,我們可以使用QtConcurrent。
一、QtConcurrent::run()
QtConcurrent::run()是最簡單的單任務(wù)線程實現(xiàn)。在寫具體代碼之前,我們需要先在pro文件中添加相應(yīng)模塊:
QT += concurrent
然后添加頭文件:
#include <QtConcurrent>
和之前一樣,通過點擊界面按鈕觸發(fā)測試代碼:
int compute(int a, int b) {
QThread::msleep(500);
return a + b;
}
void MainWindow::on_btn_thread_start_3_clicked()
{
static int a = 10, b = 20;
a++;
b++;
// 1?? 啟動異步任務(wù)
QFuture<int> future = QtConcurrent::run(compute, a, b);
// 2?? 創(chuàng)建 watcher 監(jiān)聽完成事件
QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);
// 3?? 連接 finished 信號(在主線程觸發(fā)?。?
connect(watcher, &QFutureWatcher<int>::finished, this, [watcher]() {
qDebug() << "Result:" << watcher->result();
watcher->deleteLater(); // 自動清理內(nèi)存
});
// 4?? 將 future 綁定到 watcher
watcher->setFuture(future);
}
啟動任務(wù)的時候,需要傳遞任務(wù)函數(shù)和參數(shù):
QFuture<int> future = QtConcurrent::run(compute, a, b);
返回值是第一個可監(jiān)聽的對象,代表一個未來會得到的的結(jié)果。
這一步是同步不阻塞的,隨后我們要馬上對future對象進(jìn)行觀察。注意QFuture本身是沒有信號的,因此無法使用信號槽。所以我們要單獨設(shè)置一個QFutureWatcher來進(jìn)行觀察,最后綁定future。代碼比較簡單,就不詳細(xì)說了。注意watcher要在最終的槽函數(shù)中析構(gòu)哦。
可以看到compute任務(wù)函數(shù)中,除了簡單的加法運(yùn)算之外,還特意做了線程延時。
最后,我們運(yùn)行代碼:

每次點擊都會疊加運(yùn)算,并且界面完全不阻塞。
這種寫法特別適合做簡單但有可能造成阻塞的任務(wù),相比于特意重載QThread或使用moveToThread,簡直是清爽多了。(以前工作不太懂,所以也沒用過,實在可惜。)
結(jié)合我過往的開發(fā)經(jīng)驗,說幾個可能的應(yīng)用場景:
(1)批量加載:本地有多個圖片文件需要加載讀取,甚至還要進(jìn)行尺寸壓縮
(2)多點繪圖:QImage中需要繪制多線段,這常常伴隨鼠標(biāo)滑動繪圖,為了不阻塞主線程影響流暢度,應(yīng)當(dāng)考慮線程。
二、QtConcurrent::mapped()
除此之外,QtConcurrent也提供了其他的方式,比如QtConcurrent::mapped()(并行轉(zhuǎn)換)和 QtConcurrent::filtered()(并行過濾)。
我嘗試了mapped的方式,它是一種批量的線程實現(xiàn),在某些場合下挺實用的。
接下來,嘗試一下圖片的批量加載,并進(jìn)行灰度轉(zhuǎn)換,最后彈窗顯示出來。
代碼如下:
QImage convertToGrayscale(const QString& filePath)
{
// 模擬阻塞
QThread::msleep(2000);
QImage img(filePath);
if (img.isNull()) {
qWarning() << "Failed to load:" << filePath;
return QImage(); // 返回空圖
}
// 轉(zhuǎn)為黑白(灰度)
return img.convertToFormat(QImage::Format_Grayscale8);
}
void MainWindow::on_btn_thread_start_3_1_clicked()
{
// 1. 選擇多個圖片文件(最多10個)
QStringList filePaths = QFileDialog::getOpenFileNames(
this,
"選擇圖片(最多10張)",
QDir::homePath(),
"Images (*.png *.jpg *.jpeg *.bmp *.gif)"
);
if (filePaths.isEmpty()) {
return;
}
if (filePaths.size() > 10) {
QMessageBox::warning(this, "警告", "最多只能選擇10張圖片!");
filePaths = filePaths.mid(0, 10);
}
// 2. 禁用按鈕,防止重復(fù)點擊
// m_convertButton->setEnabled(false);
// 3. 啟動并發(fā)轉(zhuǎn)換
QFuture<QImage> future = QtConcurrent::mapped(filePaths, convertToGrayscale);
// 4. 設(shè)置 watcher 監(jiān)聽完成
if (!m_watcher) {
m_watcher = new QFutureWatcher<QImage>(this);
connect(m_watcher, &QFutureWatcher<QImage>::finished,
this, &MainWindow::onConversionFinished);
}
m_watcher->setFuture(future);
}
void MainWindow::onConversionFinished()
{
// 5. 獲取所有結(jié)果(順序與輸入 filePaths 一致)
QList<QImage> results = m_watcher->future().results();
// 6. 為每張有效圖片彈窗顯示
int validCount = 0;
for (int i = 0; i < results.size(); ++i) {
if (!results[i].isNull()) {
QString title = QString("結(jié)果 %1").arg(++validCount);
showImageInDialog(results[i], title);
}
}
// 7. 恢復(fù)按鈕
// m_convertButton->setEnabled(true);
if (validCount == 0) {
QMessageBox::information(this, "提示", "沒有成功加載任何圖片。");
}
}
void MainWindow::showImageInDialog(const QImage &img, const QString &title)
{
QDialog* dialog = new QDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose); // 關(guān)閉時自動 delete
dialog->setWindowTitle(title);
dialog->resize(600, 400);
QLabel* label = new QLabel(dialog);
label->setPixmap(QPixmap::fromImage(img).scaled(
dialog->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
label->setAlignment(Qt::AlignCenter);
QVBoxLayout* layout = new QVBoxLayout(dialog);
layout->addWidget(label);
dialog->setLayout(layout);
dialog->show();
}
我們關(guān)注一下這里:
QFuture<QImage> future = QtConcurrent::mapped(filePaths, convertToGrayscale);
先是參數(shù),后是接口,這一點與run有區(qū)別。接口的參數(shù)是QString,但傳遞的參數(shù)卻是QStringList,也就是說qt會自動幫我們適應(yīng),處理批量線程??梢钥吹椒祷刂狄琅f是QFuture,它的參數(shù)是QImage,而不是批量的QList<QImage>,這一點要注意哦。
同樣的任務(wù)函數(shù)做了一點延時模擬阻塞,順便轉(zhuǎn)換了下灰度。
最后就是完成的槽函數(shù),這代表所有任務(wù)都已經(jīng)完成。
QList<QImage> results = m_watcher->future().results();
返回值可以通過results接口獲取。之后就是常規(guī)的彈窗,就不多解釋了。
運(yùn)行代碼,查看結(jié)果:

在延時的兩秒時間內(nèi),界面完全沒有被阻塞,隨后三個彈窗一起彈出來,效果還是很好的。
三、總結(jié)
有關(guān)QtConcurrent的其他接口測試就先忽略了。光是run和mapped兩種方式,我就已經(jīng)覺得非常實用了。
以下是QtConcurrent和QThread+Worker的對比:

可以看到,QtConcurrent更適合用作一次性的計算任務(wù),特別是簡單一次性的并行任務(wù)。如果是需要長期運(yùn)行的任務(wù),如while循環(huán)獲取,還是采用QThread+Worker比較好。
到此這篇關(guān)于Qt線程QtConcurrent模塊的使用的文章就介紹到這了,更多相關(guān)Qt QtConcurrent模塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中二進(jìn)制數(shù)據(jù)序列化和反序列化詳解
這篇文章主要為大家詳細(xì)介紹了C++中二進(jìn)制數(shù)據(jù)序列化和反序列化的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解下2023-11-11
C語言數(shù)組學(xué)習(xí)之特殊矩陣的壓縮存儲
矩陣在計算機(jī)圖形學(xué)、工程計算中都占有舉足輕重的地位,本文將討論如何將矩陣更有效地存儲在內(nèi)存中,并且能夠方便地提取矩陣中的元素。感興趣的同學(xué)可以了解一下2021-12-12
深入解析C++中的構(gòu)造函數(shù)和析構(gòu)函數(shù)
析構(gòu)函數(shù):在撤銷對象占用的內(nèi)存之前,進(jìn)行一些操作的函數(shù)。析構(gòu)函數(shù)不能被重載,只能有一個2013-09-09
C語言進(jìn)階教程之字符函數(shù)和字符串函數(shù)
C語言中對字符和字符串的處理很是頻繁,但是C語言本身是沒有字符串類型的,字符串通常放在常量字符串中或者字符數(shù)組中,下面這篇文章主要給大家介紹了關(guān)于C語言進(jìn)階教程之字符函數(shù)和字符串函數(shù)的相關(guān)資料,需要的朋友可以參考下2022-11-11
C++命名空間using?namespace?std是什么意思
namespace中文意思是命名空間或者叫名字空間,傳統(tǒng)的C++只有一個全局的namespace,下面這篇文章主要給大家介紹了關(guān)于C++命名空間using?namespace?std是什么意思的相關(guān)資料,需要的朋友可以參考下2023-01-01

