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

每次點(diǎn)擊都會(huì)疊加運(yùn)算,并且界面完全不阻塞。
這種寫(xiě)法特別適合做簡(jiǎn)單但有可能造成阻塞的任務(wù),相比于特意重載QThread或使用moveToThread,簡(jiǎn)直是清爽多了。(以前工作不太懂,所以也沒(méi)用過(guò),實(shí)在可惜。)
結(jié)合我過(guò)往的開(kāi)發(fā)經(jīng)驗(yàn),說(shuō)幾個(gè)可能的應(yīng)用場(chǎng)景:
(1)批量加載:本地有多個(gè)圖片文件需要加載讀取,甚至還要進(jìn)行尺寸壓縮
(2)多點(diǎn)繪圖:QImage中需要繪制多線(xiàn)段,這常常伴隨鼠標(biāo)滑動(dòng)繪圖,為了不阻塞主線(xiàn)程影響流暢度,應(yīng)當(dāng)考慮線(xiàn)程。
二、QtConcurrent::mapped()
除此之外,QtConcurrent也提供了其他的方式,比如QtConcurrent::mapped()(并行轉(zhuǎn)換)和 QtConcurrent::filtered()(并行過(guò)濾)。
我嘗試了mapped的方式,它是一種批量的線(xiàn)程實(shí)現(xiàn),在某些場(chǎng)合下挺實(shí)用的。
接下來(lái),嘗試一下圖片的批量加載,并進(jìn)行灰度轉(zhuǎn)換,最后彈窗顯示出來(lái)。
代碼如下:
QImage convertToGrayscale(const QString& filePath)
{
// 模擬阻塞
QThread::msleep(2000);
QImage img(filePath);
if (img.isNull()) {
qWarning() << "Failed to load:" << filePath;
return QImage(); // 返回空?qǐng)D
}
// 轉(zhuǎn)為黑白(灰度)
return img.convertToFormat(QImage::Format_Grayscale8);
}
void MainWindow::on_btn_thread_start_3_1_clicked()
{
// 1. 選擇多個(gè)圖片文件(最多10個(gè))
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ù)點(diǎn)擊
// m_convertButton->setEnabled(false);
// 3. 啟動(dòng)并發(fā)轉(zhuǎn)換
QFuture<QImage> future = QtConcurrent::mapped(filePaths, convertToGrayscale);
// 4. 設(shè)置 watcher 監(jiān)聽(tīng)完成
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, "提示", "沒(méi)有成功加載任何圖片。");
}
}
void MainWindow::showImageInDialog(const QImage &img, const QString &title)
{
QDialog* dialog = new QDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose); // 關(guān)閉時(shí)自動(dòng) 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ù),后是接口,這一點(diǎn)與run有區(qū)別。接口的參數(shù)是QString,但傳遞的參數(shù)卻是QStringList,也就是說(shuō)qt會(huì)自動(dòng)幫我們適應(yīng),處理批量線(xiàn)程??梢钥吹椒祷刂狄琅f是QFuture,它的參數(shù)是QImage,而不是批量的QList<QImage>,這一點(diǎn)要注意哦。
同樣的任務(wù)函數(shù)做了一點(diǎn)延時(shí)模擬阻塞,順便轉(zhuǎn)換了下灰度。
最后就是完成的槽函數(shù),這代表所有任務(wù)都已經(jīng)完成。
QList<QImage> results = m_watcher->future().results();
返回值可以通過(guò)results接口獲取。之后就是常規(guī)的彈窗,就不多解釋了。
運(yùn)行代碼,查看結(jié)果:

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

可以看到,QtConcurrent更適合用作一次性的計(jì)算任務(wù),特別是簡(jiǎn)單一次性的并行任務(wù)。如果是需要長(zhǎng)期運(yùn)行的任務(wù),如while循環(huán)獲取,還是采用QThread+Worker比較好。
到此這篇關(guān)于Qt線(xiàn)程QtConcurrent模塊的使用的文章就介紹到這了,更多相關(guān)Qt QtConcurrent模塊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Qt多線(xiàn)程安全更新QLabel的實(shí)現(xiàn)
- Qt優(yōu)雅實(shí)現(xiàn)線(xiàn)程安全單例模式(模板化和自動(dòng)清理)
- Qt中實(shí)現(xiàn)多線(xiàn)程導(dǎo)出數(shù)據(jù)功能的四種方式小結(jié)
- Qt 中線(xiàn)程池QRunnable和QThreadPool的使用
- Qt中線(xiàn)程常用通信方式介紹
- 一文帶你了解Qt多線(xiàn)程的實(shí)現(xiàn)方式
- QT中線(xiàn)程池QThreadPool類(lèi)概念和使用方法詳解
- Qt線(xiàn)程QThread開(kāi)啟和安全退出的實(shí)現(xiàn)
- Qt 線(xiàn)程遷移(moveToThread)機(jī)制完整實(shí)戰(zhàn)指南
相關(guān)文章
C/C++實(shí)現(xiàn)的MD5哈希校驗(yàn)的示例代碼
MD5算法是一種廣泛使用的 Hash 算法,常用于確保信息傳輸?shù)耐暾耘c一致性,本文主要介紹了C/C++實(shí)現(xiàn)的MD5哈希校驗(yàn)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10
簡(jiǎn)單談?wù)凜++ 頭文件系列之(algorithm)
<algorithm>是c++特有的STL模板的算法頭文件 包含了一些特定的算法函數(shù) 包括sort(),stable_sort(),partical_sort(),nth_element()等常用的算法函數(shù)2017-02-02
在vs2017上配置AppGameKit庫(kù)的圖文教程
這篇文章主要介紹了在vs2017上配置AppGameKit庫(kù)的教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
C++實(shí)現(xiàn)學(xué)校人員管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)學(xué)校人員管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

