preface(前言)
在Qt中,信號(hào)槽機(jī)制是允許對(duì)象之間通信的核心特性。
在處理線程時(shí),非常必要了解信號(hào)和插槽在多線程環(huán)境中的工作方式。
關(guān)于connect函數(shù)的“第五個(gè)參數(shù)”,指的是connect()
方法中的Qt::ConnectionType
參數(shù)。此參數(shù)確定連接的類型,這在多線程應(yīng)用程序中非常重要。
qt Connect函數(shù)的第五個(gè)參數(shù)(這里專門(mén)指:QueuedConnection)-多線程編程的一些原理
1)初步理解
現(xiàn)在讓我們通過(guò)一個(gè)例子深入研究Qt的信號(hào)槽機(jī)制中的連接連接的概念,我將引導(dǎo)你理解它的工作原理。
假設(shè)在一個(gè)Qt應(yīng)用程序中有兩個(gè)線程:線程A和線程B
。線程A是主GUI線程,而線程B是后臺(tái)工作線程
。你希望線程B在完成任務(wù)時(shí)通知線程A
,因此你將使用Qt的信號(hào)槽機(jī)制
來(lái)實(shí)現(xiàn)這一點(diǎn)。
在典型
的直接連接(單線程應(yīng)用程序的默認(rèn)設(shè)置)中,當(dāng)發(fā)出信號(hào)時(shí),插槽立即執(zhí)行
。但是,在多線程
方案中,這種立即執(zhí)行
可能不安全或不可行,因?yàn)榭赡苄枰?code>插槽來(lái)操作GUI組件
或訪問(wèn)僅可從主線程安全訪問(wèn)
的資源。
這就是連接的作用。
使用QueuedConnection
,當(dāng)線程B
發(fā)出信號(hào)時(shí),該信號(hào)被放置在線程A
的事件隊(duì)列
中,所以不會(huì)立即執(zhí)行。然后,當(dāng)線程A
的事件循環(huán)下一次被處理
時(shí),它在隊(duì)列中檢測(cè)到這個(gè)信號(hào),并執(zhí)行相應(yīng)的slot函數(shù)
。這樣,即使信號(hào)是從不同的線程發(fā)出的,slot函數(shù)也會(huì)在接收對(duì)象所在的線程中安全地執(zhí)行,從而防止任何線程安全問(wèn)題。
2)加深理解
為了幫助鞏固這一概念,你能想到一個(gè)簡(jiǎn)單的場(chǎng)景,在這種類型的連接是必要的嗎?
例如,假設(shè)線程B正在執(zhí)行文件下載,您希望在完成此下載后更新GUI(線程A)中的進(jìn)度條。在這種情況下,您將如何設(shè)置信號(hào)和插槽,使用一個(gè)連接?
現(xiàn)在請(qǐng)關(guān)閉博客,給你5分鐘來(lái)思考…
two thousand years later…
下面具體展開(kāi)來(lái)講講:
我可以使用QueuedConnection將線程A
和B
進(jìn)行連接,線程B
邊下載邊計(jì)算 已下載/總文件大小
,分別在10%,20%,30%...100%
向線程A發(fā)送一個(gè)信號(hào),該信號(hào)的參數(shù)
就是下載的百分比
,然后線程A
收到后,根據(jù)線程B的參數(shù)來(lái)更新GUI
demo
在寫(xiě)demo之前,我有一個(gè)疑問(wèn)?qt的connect函數(shù)是同步的還是異步的?
Q t Qt Qt的 c o n n e c t connect connect 函數(shù)用于將信號(hào)與槽連接起來(lái),但它本身既不是同步的也不是異步的。這個(gè)函數(shù)的作用是建立一個(gè)連接,當(dāng)信號(hào)發(fā)生時(shí),相應(yīng)的槽函數(shù)會(huì)被調(diào)用。
關(guān)于同步或異步的行為,這取決于信號(hào)和槽函數(shù)的執(zhí)行方式,而不是 connect 函數(shù)本身。
在 Qt 中,
- 如果信號(hào)和槽都在同一個(gè)線程中,槽函數(shù)的調(diào)用通常是同步的,即在信號(hào)發(fā)射后立即執(zhí)行。
- 如果信號(hào)和槽位于不同的線程,Qt 使用消息隊(duì)列來(lái)傳遞信號(hào),這時(shí)槽函數(shù)的調(diào)用是異步的,會(huì)在目標(biāo)線程的事件循環(huán)中稍后執(zhí)行。
因此,connect 函數(shù)的作用是建立信號(hào)和槽之間的連接,而信號(hào)到槽的調(diào)用機(jī)制決定了同步或異步行為。
單線程
實(shí)現(xiàn)邊下載某個(gè)網(wǎng)頁(yè)的文件,邊實(shí)時(shí)更新當(dāng)前的進(jìn)度條
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QProgressBar>
#include <QFile>
#include <QUrl>
class Downloader : public QObject
{
Q_OBJECT
public:
Downloader(const QUrl& url, QProgressBar* progressBar, QObject* parent = nullptr)
: QObject(parent), url(url), progressBar(progressBar)
{
//這一行代碼僅僅是創(chuàng)建了一個(gè) QNetworkAccessManager 的實(shí)例,并將其分配給 manager 指針。這一行代碼本身并沒(méi)有建立連接或發(fā)起網(wǎng)絡(luò)請(qǐng)求。
manager = new QNetworkAccessManager(this);
//當(dāng)網(wǎng)絡(luò)請(qǐng)求完成或下載進(jìn)度發(fā)生變化時(shí),相關(guān)的槽函數(shù)將被調(diào)用。
//即,下載完成后,會(huì)執(zhí)行onFinished函數(shù)進(jìn)行收尾工作
connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onFinished);
}
void startDownload()
{
reply = manager->get(QNetworkRequest(url));//這句話是異步的,并不會(huì)阻塞
//當(dāng)下載進(jìn)度發(fā)生變化時(shí),相關(guān)的槽函數(shù)將被調(diào)用。
connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgress);
}
private slots:
void onFinished(QNetworkReply* reply)
{
if (reply->error() == QNetworkReply::NoError)//下載很順利,沒(méi)有錯(cuò)誤
{
// 下載完成
QByteArray data = reply->readAll();
// 處理下載的數(shù)據(jù)
//to do ...
//eg:將下載文件解壓、對(duì)比、等等
// 清理資源
reply->deleteLater();
manager->deleteLater();
}
else
{
// 處理下載錯(cuò)誤
//搞個(gè)QMessageBox提示框報(bào)錯(cuò)
}
}
//當(dāng)下載進(jìn)度發(fā)生變化時(shí),相關(guān)的槽函數(shù)將被調(diào)用。
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
// 更新進(jìn)度條
int progress = static_cast<int>(bytesReceived * 100 / bytesTotal);
progressBar->setValue(progress);
}
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
QUrl url;
QProgressBar* progressBar;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 創(chuàng)建主窗口
QWidget mainWindow;
mainWindow.setWindowTitle("下載并更新進(jìn)度條示例");
// 創(chuàng)建進(jìn)度條
QProgressBar progressBar(&mainWindow);
progressBar.setGeometry(10, 10, 280, 30);
// 下載文件的URL
QUrl downloadUrl("https://example.com/examplefile.txt");
// 創(chuàng)建Downloader實(shí)例并開(kāi)始下載
Downloader downloader(downloadUrl, &progressBar);//實(shí)例
downloader.startDownload();//下載
// 顯示主窗口
mainWindow.show();
return a.exec();
}
#include "main.moc"
解析
因?yàn)?reply = manager->get(QNetworkRequest(url));
是異步的,,因?yàn)槭菃尉€程,所以connect
函數(shù)是同步的(即在信號(hào)發(fā)射后立即執(zhí)行)
.
使用了 Qt 的 QNetworkAccessManager 和 QNetworkReply 來(lái)處理網(wǎng)絡(luò)下載,并且更新了 GUI 的 QProgressBar 來(lái)顯示下載進(jìn)度。這個(gè)設(shè)計(jì)考慮到了 Qt 的事件驅(qū)動(dòng)和異步處理機(jī)制,因此在理論上,GUI 不應(yīng)該在下載過(guò)程中出現(xiàn)卡頓
。
- 下面是關(guān)于代碼中的幾個(gè)關(guān)鍵點(diǎn)的解釋:
-
異步網(wǎng)絡(luò)請(qǐng)求:使用 QNetworkAccessManager 的 get 方法來(lái)發(fā)起網(wǎng)絡(luò)請(qǐng)求是
異步的
。這意味著get 方法會(huì)立即返回,而不會(huì)等待下載完成
。下載在后臺(tái)進(jìn)行,不會(huì)阻塞主線程
。 -
信號(hào)和槽機(jī)制:當(dāng)下載進(jìn)度更新或下載完成時(shí),將觸發(fā)信號(hào),如 downloadProgress 和 finished。
這些信號(hào)連接到槽函數(shù) onDownloadProgress 和 onFinished
,它們會(huì)在主線程
中被調(diào)用,但由于它們的執(zhí)行時(shí)間通常很短,不太可能導(dǎo)致 GUI 卡頓
。 -
GUI更新:
進(jìn)度條的更新發(fā)生在 onDownloadProgress 槽中,這個(gè)操作通常很快,不會(huì)對(duì) GUI 性能產(chǎn)生顯著影響
。
綜上所述,如果下載任務(wù)和數(shù)據(jù)處理不是特別重或復(fù)雜,這個(gè)設(shè)計(jì)通常不會(huì)導(dǎo)致 GUI 卡頓。但是,如果在 onFinished 槽中的數(shù)據(jù)處理非常繁重(例如,處理非常大的文件或進(jìn)行復(fù)雜的數(shù)據(jù)操作),那么可能會(huì)影響 GUI 的響應(yīng)性。在這種情況下,可以考慮將數(shù)據(jù)處理任務(wù)移到另一個(gè)線程中去,以保持 GUI 的流暢性。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-811567.html
多線程
GUI:主線程A
下載的任務(wù):線程B
線程B在下載的同時(shí),會(huì)給計(jì)算下載的百分比,并將百分比發(fā)送給線程A文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-811567.html
#include <QThread>
#include <QObject>
#include <QDebug>
#include <...>
class Downloader : public QObject {
Q_OBJECT
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
QUrl url;
public slots:
void download(QString Url) {// 六
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onFinished);
reply = manager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgress);
/*
for (int downloaded = 10; downloaded <= totalSize; downloaded += 10) {
QThread::msleep(100); // 模擬下載過(guò)程, 使用msleep代替sleep以增加進(jìn)度更新的頻率
int percent = static_cast<int>(100.0 * downloaded / totalSize);
emit progress(percent);
}
emit finished();
*/
}
void onFinished(QNetworkReply* reply)
{
emit finished(reply);
}
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
int progress = static_cast<int>(bytesReceived * 100 / bytesTotal);
emit progress(progress);
}
signals:
void progress(int percent);
void finished();
};
/
class MyWidget : public QObject {
Q_OBJECT
public:
MyWidget() {// 二
// 將下載器移至工作線程
downloader.moveToThread(&workerThread);
// 連接開(kāi)始下載的信號(hào)到下載器的下載槽// 五
connect(this, &MyWidget::startDownload, &downloader, &Downloader::download, Qt::QueuedConnection);
// 連接下載器的進(jìn)度信號(hào)到處理進(jìn)度的槽
connect(&downloader, &Downloader::progress, this, &MyWidget::handleProgress, Qt::QueuedConnection);
// 連接下載完成信號(hào)
connect(&downloader, &Downloader::finished, this, &MyWidget::handleFinished, Qt::QueuedConnection);
// 啟動(dòng)工作線程
workerThread.start();
}
~MyWidget() {
workerThread.quit();
workerThread.wait();
}
void startDownloading(QString Url) {// 四
emit startDownload(Url); // 發(fā)送信號(hào)來(lái)開(kāi)始下載
}
public slots:
void handleProgress(int percent) {
qDebug() << "Download progress:" << percent << "%";
// 這里可以更新 GUI 進(jìn)度條
}
void handleFinished(QNetworkReply* reply) {
if (reply->error() == QNetworkReply::NoError)//下載很順利,沒(méi)有錯(cuò)誤
{
qDebug() << "Download finished!";
// 處理下載完成事件,例如關(guān)閉進(jìn)度條、彈出通知等操作
// 下載完成
QByteArray data = reply->readAll();
// 處理下載的數(shù)據(jù)
//to do ...
//eg:將下載文件解壓、對(duì)比、等等
// 清理資源
reply->deleteLater();
manager->deleteLater();
}
else
{
// 處理下載錯(cuò)誤
//搞個(gè)QMessageBox提示框報(bào)錯(cuò)
}
}
signals:
void startDownload(int totalSize);
private:
QThread workerThread;
Downloader downloader;
};
#include "main.moc"
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
MyWidget widget;// 一
QString Url = "https://example.com/examplefile.txt";
widget.startDownloading(Url); // 三
return app.exec();
}
解析
// 連接開(kāi)始下載的信號(hào)到下載器的下載槽
connect(this, &MyWidget::startDownload, &downloader, &Downloader::download, Qt::QueuedConnection);
// 連接下載器的進(jìn)度信號(hào)到處理進(jìn)度的槽
connect(&downloader, &Downloader::progress, this, &MyWidget::handleProgress, Qt::QueuedConnection);
// 連接下載完成信號(hào)
connect(&downloader, &Downloader::finished, this, &MyWidget::handleFinished, Qt::QueuedConnection);
附件
- 分別實(shí)現(xiàn)單線程與多線程下,qt下載文件并實(shí)時(shí)更新進(jìn)度條的流程圖
到了這里,關(guān)于The fifth parameter of the qt slot function(qt Connect函數(shù)的第五個(gè)參數(shù))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!