力學篤行(四)Qt 線程與信號槽

線程與信號槽

  • 1. 主窗口(MainWindow)主線程
  • 2. 線程
    • 2.1 QThread
    • 2.2 QtConcurrent::run()
    • 2.3 thread 的調用方式
  • 3. 信號槽
    • 3.1 connect
    • 3.2 元對象系統中注冊自定義數據類型
  • 附錄一 信號槽機制與主線程進行通信示例

1. 主窗口(MainWindow)主線程

在Qt中,線程和信號槽機制是兩個核心概念,它們結合使用可以實現多線程編程,并在不同線程之間進行通信。

這里提一個主線程的概念,主窗口(MainWindow)通常是應用程序的主要界面,它的生命周期和事件循環是由主線程管理的。雖然可以在主窗口的代碼中創建和操作其他線程,但通常情況下,長時間運行的任務或耗時操作應該在單獨的線程中執行,以保持主線程的響應性。

  1. 主線程的任務
    主線程負責處理用戶界面交互、事件響應和更新UI等任務。長時間運行的任務應該在單獨的線程中執行,以避免阻塞主線程并保持應用程序的響應性。

  2. 線程對象的生命周期
    在 mainwindow.cpp 中創建的線程對象 默認是屬于主線程 的,因為它們是在主線程的上下文中創建的。即使在 mainwindow.cpp 中創建了一個 QThread 對象和其他工作線程對象,這些對象本身仍然屬于主線程的管理

  3. 使用信號槽進行跨線程通信
    在 mainwindow.cpp 中創建的線程對象可以通過信號槽機制與其他對象或線程進行通信。這意味著你可以將主線程的信號連接到工作線程的槽,或者反過來,從工作線程發射信號并在主線程中處理。通過正確使用信號槽,可以實現跨線程的通信和數據傳輸,而不會阻塞主線程的事件循環。

2. 線程

2.1 QThread

Qt中使用QThread類來管理線程。一般來說,你可以通過以下步驟使用QThread:

  1. 創建一個線程類: 繼承自QThread,重寫run()方法,在run()方法中編寫線程執行的代碼。
  2. 啟動線程: 通過創建線程對象并調用start()方法來啟動線程。
  3. 線程的執行控制: 通常在run()方法中編寫線程的主要邏輯。可以通過信號槽機制在主線程和子線程之間進行通信。

下面是一個簡單的示例,演示如何使用QThread類創建一個線程并啟動它:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 自定義的線程類
class WorkerThread : public QThread
{
public:void run() override{qDebug() << "Worker Thread ID: " << QThread::currentThreadId();// 執行一些耗時的任務for (int i = 0; i < 5; ++i) {qDebug() << "Counting " << i;sleep(1); // 模擬耗時操作}}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);qDebug() << "Main Thread ID: " << QThread::currentThreadId();WorkerThread thread;thread.start(); // 啟動線程// 這里可以繼續在主線程中執行其他任務return a.exec();
}

2.2 QtConcurrent::run()

在 Qt 中,QtConcurrent::run() 函數是用于在 后臺線程 中執行函數或Lambda表達式的便捷方法。它允許在不需要手動管理線程的情況下,并行地執行耗時的操作,從而避免主線程的阻塞和提高程序的響應性。

  • 線程管理: 是一個線程安全的函數,它會在 Qt 的線程池中執行任務,避免了直接操作底層線程的復雜性。Qt 會自動管理線程池的大小和任務的分發,以提高效率和性能。
  • 線程安全性: 由于任務在后臺線程中執行,必須確保訪問共享資源時的線程安全性,例如使用互斥量 (QMutex) 或原子操作來保護共享數據的訪問。
  • UI 更新: 后臺線程中不能直接更新用戶界面 (UI),如需要在任務完成后更新 UI,可以使用信號和槽機制,或者在任務完成后通過主線程的事件循環執行相關操作。
  1. 基本語法
QFuture<void> QtConcurrent::run(Function function);
QFuture<void> QtConcurrent::run(Callable callable);

其中:

  • Function 是一個函數指針,指向要在后臺線程中執行的函數。
  • Callable 是一個可調用對象,可以是函數對象或Lambda表達式等。
  1. Lambda表達式
QtConcurrent::run([&]() {// 在后臺線程中執行的代碼// 可以訪問外部變量
});

Lambda表達式內部可以訪問外部的變量,使用 [&] 捕捉方式可以捕捉所有外部變量的引用,使得在后臺線程中可以安全地訪問和修改這些變量。

以下是一個簡單的示例,演示了如何使用 QtConcurrent::run() 執行一個耗時任務:

#include <QtConcurrent/QtConcurrent>// 定義一個耗時任務
void performTask(int value) {// 模擬耗時操作for (int i = 0; i < value; ++i) {QThread::msleep(100); // 模擬耗時操作,每次休眠100毫秒qDebug() << "Task progress:" << i;}
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int parameter = 5; // 任務的參數// 使用 QtConcurrent::run 啟動一個后臺任務QFuture<void> future = QtConcurrent::run([&]() {performTask(parameter);});// 等待任務完成future.waitForFinished();qDebug() << "Task completed!";return a.exec();
}

在這個示例中,performTask 函數模擬了一個耗時的任務,使用 QtConcurrent::run() 啟動一個后臺線程執行這個任務,并通過 QFuture 跟蹤任務的執行狀態和結果。

2.3 thread 的調用方式

參數說明
detach啟動的線程自主在后臺運行,當前的代碼繼續主下執行,不等待新線程結束。
join等待啟動的線程完成,才會繼續往下執行。

3. 信號槽

信號槽是Qt中一種用于對象間通信的機制,它不僅可以在同一線程中使用,還可以跨線程使用。在跨線程的情況下,信號槽機制能夠確保線程安全地進行通信。

  1. 定義信號和槽: 信號是類似于函數的成員,可以被其他對象連接到。槽是接收信號的函數,它們的聲明方式與普通的C++成員函數相似,但使用signals和slots關鍵字來定義。

  2. 連接信號和槽: 使用connect()函數將信號與槽連接起來。Qt中支持跨線程的信號槽連接,當一個信號發射時,與之連接的槽可以在目標線程中被執行。

3.1 connect

在Qt中,使用connect()函數將信號與槽連接起來是實現對象間通信的核心機制之一。通過信號與槽的連接,可以在一個對象發出信號時,觸發另一個對象的槽函數執行。下面是幾種常見的連接方式示例:

  1. 普通連接方式
    最基本的連接方式是直接使用connect()函數將信號與槽連接起來。這種方式適用于信號和槽的參數列表完全匹配的情況。
// 連接 sender 對象的 signal 信號到 receiver 對象的槽函數 slot
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

在這里:

  • sender 是發出信號的對象。
  • SIGNAL(signal()) 是宏,用于指定信號的名稱。
  • receiver 是接收信號的對象。
  • SLOT(slot()) 是宏,用于指定槽函數的名稱。
  1. 使用函數指針連接方式
    如果信號和槽的參數列表完全匹配,并且你希望避免使用宏,可以使用函數指針的方式連接。
// 連接 sender 對象的 signal 信號到 receiver 對象的槽函數 slot
connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);

這種方式使用了C++11引入的新特性,使用函數指針取代了宏,更加類型安全。

  1. 使用Lambda表達式連接方式
    從Qt5開始,還可以使用Lambda表達式連接信號和槽。Lambda表達式可以捕獲外部變量,使得連接的代碼更加靈活和簡潔。

三種常用使用方法

// 使用Lambda表達式連接 sender 對象的 signal 信號
connect(sender, &SenderClass::signal, [=](double* value) {// Lambda表達式內的代碼,可以執行任意操作// 這里可以訪問外部變量receiver->slot();
});
connect(sender, &SenderClass::signal, [&](double* value) {// Lambda表達式內的代碼,可以執行任意操作// 這里可以訪問外部變量receiver->slot();
});
connect(sender, &SenderClass::signal, [this](double* value) {// Lambda表達式內的代碼,可以執行任意操作// 這里可以訪問外部變量receiver->slot();
});

Lambda表達式內部可以編寫需要執行的邏輯,可以訪問當前上下文中的變量。

捕獲方式捕獲內容權限
[=]捕捉所有外部變量的副本只能訪問但不能修改
[&]捕捉所有外部變量的引用可以修改這個信號參數的值
[this]捕捉當前對象的所有成員變量Lambda表達式內部可以訪問當前對象的成員變量,但不能修改它們的值

第四種使用方法:訪問和修改當前對象的成員變量

connect(sender, &SenderClass::signal, this, [this](double* value) {// Lambda表達式內的代碼,可以執行任意操作// 這里可以訪問外部變量receiver->slot();
});
  • 訪問成員變量: 適合于連接信號時需要訪問當前對象的成員變量的情況,例如在槽函數中需要使用類的狀態或配置信息。
  • 修改外部變量: 由于使用了 [this] 捕捉方式,Lambda 表達式內部也能夠修改當前對象的成員變量的值。
  1. 使用隊列連接方式
    在Qt中,還可以使用Qt::QueuedConnection來連接信號和槽,這種方式將信號放入接收對象的事件隊列中,在接收對象的事件循環中處理,即使信號和槽在不同的線程中也能正常工作。
// 使用隊列連接方式,將 sender 對象的 signal 信號連接到 receiver 對象的槽函數 slot
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()), Qt::QueuedConnection);

這種連接方式適用于需要在不同線程間進行通信的情況。

  1. 指定連接類型的應用

connect第五個參數

參數說明補充
Qt::AutoConnection如果信號和槽在同一線程,則使用Qt::DirectConnection;如果在不同線程,則使用Qt::QueuedConnection。默認值,使用這個值則連接類型會在信號發送時決定。如果接收者和發送者在同一個線程,則自動使用Qt::DirectConnection類型。如果接收者和發送者不在一個線程,則自動使用Qt::QueuedConnection類型。
Qt::DirectConnection直接調用槽函數,如果信號和槽在同一線程中,相當于直接調用函數。槽函數會在信號發送的時候直接被調用,槽函數運行于信號發送者所在線程。效果看上去就像是直接在信號發送位置調用了槽函數。這個在多線程環境下比較危險,可能會造成奔潰。
Qt::QueuedConnection將信號投遞到接收者的事件隊列中,在接收者的事件循環中處理,適合跨線程通信。槽函數在控制回到接收者所在線程的事件循環時被調用,槽函數運行于信號接收者所在線程。發送信號之后,槽函數不會立刻被調用,等到接收者的當前函數執行完,進入事件循不之后,槽函數才會被調用。多線程環境下一般用這個。
Qt::BlockingQueuedConnection特殊的隊列連接方式,阻塞發送方直到槽函數執行完畢。槽函數的調用時機與Qt::QueuedConnection一致,不過發送完信號后發送者所在線程會阻塞,直到槽函數運行完。接收者和發送者絕對不能在一個線程,否則程序會死鎖。在多線程間需要同步的場合可能需要這個。
Qt::UniqueConnectionQt::UniqueConnection用于確保同一連接不會被重復建立。如果同一組件(sender 和 receiver)已經有一個相同類型的連接存在,則connect()函數會失敗并返回false。這種方式常用于確保只有一個唯一的連接存在,避免多次連接導致槽函數被多次調用。這個flag可以通過按位或(1)與以上四個結合在一起使用。當這個flag設置時,當某個信號和槽已經連接愛時,再進行重復的連接就會失敗。也就是避免了重復連接。
斷開連接的方法該方法雖然不是必須使用的,因為當一個對象delete之后,Qt自動取消所有連接到這個對象上面的槽。disconnect(sender,SIGNAL(signal),receiver,SLOT(slot), Qt::DirectConnection);

下面是一個簡單的示例,演示了如何使用connect()函數來連接信號與槽,并且注釋了不同連接類型的使用場景:

#include <QObject>class Sender : public QObject {Q_OBJECTpublic slots:void sendSignal() {emit someSignal();}signals:void someSignal();
};class Receiver : public QObject {Q_OBJECTpublic slots:void handleSignal() {qDebug() << "Signal received in thread: " << QThread::currentThreadId();}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);Sender sender;Receiver receiver;// 使用 Qt::AutoConnection(默認)QObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()));// 使用 Qt::DirectConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::DirectConnection);// 使用 Qt::QueuedConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::QueuedConnection);// 使用 Qt::BlockingQueuedConnectionQObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::BlockingQueuedConnection);// 使用 Qt::UniqueConnectionbool connected = QObject::connect(&sender, SIGNAL(someSignal()), &receiver, SLOT(handleSignal()),Qt::UniqueConnection);if (!connected) {qDebug() << "Failed to establish unique connection!";}// 發送信號sender.sendSignal();return app.exec();
}#include "main.moc"

3.2 元對象系統中注冊自定義數據類型

在Qt中,信號和槽(Signals and Slots)是一種強大的機制,用于在對象之間進行通信。Qt 會對于標準的數據類型(如 int、QString 等)進行內置支持,但對于自定義的數據類型(如枚舉、結構體、類等),Qt 需要能夠動態地識別和處理這些類型。因此,需要使用 qRegisterMetaType 來告知 Qt 系統如何處理這些自定義類型:

  • 注冊類型: 通過 qRegisterMetaType,Qt 能夠在運行時了解如何創建、復制和銷毀這些類型的實例。
  • 信號和槽的參數傳遞: 注冊后,可以在信號和槽的連接中使用這些自定義類型作為參數,Qt 能夠正確地處理參數的傳遞和槽函數的調用。

示例代碼

namespace Test{enum TestEnum {TestA,TestB,TestC};
}
qRegisterMetaType<Test::TestEnum>("Test::TestEnum");

附錄一 信號槽機制與主線程進行通信示例

下面是一個簡單的示例,展示了如何在 mainwindow.cpp 中創建一個工作線程,并通過信號槽機制與主線程進行通信。

// mainwindow.cpp#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>// 定義一個工作線程類
class WorkerThread : public QThread
{
public:void run() override{qDebug() << "Worker Thread ID: " << QThread::currentThreadId();// 模擬耗時操作for (int i = 0; i < 5; ++i) {qDebug() << "Counting " << i;sleep(1);}// 發射信號表示工作完成emit workFinished();}signals:void workFinished();
};MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);qDebug() << "Main Thread ID: " << QThread::currentThreadId();// 創建工作線程實例WorkerThread *workerThread = new WorkerThread();// 連接工作線程的工作完成信號到主線程的槽connect(workerThread, &WorkerThread::workFinished, this, &MainWindow::onWorkFinished);// 啟動工作線程workerThread->start();
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::onWorkFinished()
{qDebug() << "Work finished signal received in Main Thread ID: " << QThread::currentThreadId();// 這里可以處理工作線程完成后的邏輯
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/43918.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/43918.shtml
英文地址,請注明出處:http://en.pswp.cn/web/43918.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL聯合索引最左匹配原則

MySQL中的聯合索引(也叫組合索引)遵循最左匹配原則&#xff0c;即在創建聯合索引時&#xff0c;查詢條件必須從索引的最左邊開始&#xff0c;否則索引不會被使用。在聯合索引的情況下&#xff0c;數據是按照索引第一列排序&#xff0c;第一列數據相同時才會按照第二列排序。 例…

CVE-2024-27292:Docassemble任意文件讀取漏洞復現 [附POC]

文章目錄 CVE-2024-27292&#xff1a;Docassemble任意文件讀取漏洞復現 [附POC]0x01 前言0x02 漏洞描述0x03 影響版本0x04 漏洞環境0x05 漏洞復現1.訪問漏洞環境2.構造POC3.復現 0x06 修復建議 CVE-2024-27292&#xff1a;Docassemble任意文件讀取漏洞復現 [附POC] 0x01 前言 …

冒泡排序與其C語言通用連續類型排序代碼

冒泡排序與其C語言通用連續類型排序代碼 冒泡排序冒泡排序為交換排序的一種&#xff1a;動圖展示&#xff1a;冒泡排序的特性總結&#xff1a;冒泡排序排整型數據參考代碼&#xff08;VS2022C語言環境&#xff09;&#xff1a; 冒泡排序C語言通用連續類型排序代碼對比較的方式更…

法律行業守護神:知識庫+AI大模型,解鎖企業知識全周期管理

在法律行業中&#xff0c;搭建一個有效的知識庫并進行企業知識全生命周期管理確實是一項不小的挑戰。法律環境的復雜性和不斷變化的法規要求企業必須持續更新和維護其知識庫&#xff0c;以確保所有信息的準確性和實時性。 這種系統化的信息管理不僅有助于提高律師和法律顧問的…

打卡第9天-----字符串

我在自學的時候,看了卡爾的算法公開課了,有些題目我就照葫蘆畫瓢寫了一遍js代碼,差不多都寫出來了,有暴力解法,有卡爾推薦的思路和方法。話不多說,直接上題上代碼吧: 一、翻轉字符串里的單詞 leetcode題目鏈接:151. 反轉字符串中的單詞 題目描述: 給你一個字符串 s…

5個自動化面試題,助你過關斬將!

面試時&#xff0c;自動化是軟件測試高頻面試內容&#xff0c;通過學習和準備面試題&#xff0c;你會對可能遇到的問題有所準備&#xff0c;從而減輕面試時的緊張感&#xff0c;讓你在面試中穩操勝券&#xff01; 今天&#xff0c;分享一些在面試中可能會遇到的自動化測試面試…

軟件架構之測評方法

軟件架構之測評方法 第 11 章&#xff1a;測試評審方法11.1 測試方法11.1.1 軟件測試階段11.1.2 白盒測試和黑盒測試11.1.3 缺陷的分類和級別11.1.4 調試 11.2 評審方法11.3 驗證與確認11.4 測試自動化11.5 面向對象的測試 第 11 章&#xff1a;測試評審方法 軟件測試與評審是…

大學生暑假“三下鄉”社會實踐工作新聞投稿指南請查收!

近年來&#xff0c;大學生暑期“三下鄉”社會實踐工作方興未艾&#xff0c;越來越多的大學生通過參與“三下鄉”實踐工作&#xff0c;走出校園&#xff0c;深入基層&#xff0c;體驗農村生活&#xff0c;服務農民&#xff0c;促進農村經濟社會發展&#xff0c;實現了理論與實踐…

算能科技,致力于成為全球領先的通用算力供應商

算能致力于成為全球領先的定制算力提供商&#xff0c;專注于RISC-V、TPU處理器等算力產品的研發和推廣應用。公司遵循全面開源開放的生態理念&#xff0c;攜手行業伙伴推動RISC-V高性能通用計算產業落地&#xff1b;打造覆蓋“云、邊、端”的全場景產品矩陣&#xff0c;為數據中…

【eNSP模擬實驗】三層交換機實現VLAN通信

實驗需求 讓PC1和PC2能夠互相通訊&#xff0c;其中PC1在vlan10中&#xff0c;PC2在vlan20中。 實驗操作 首先把PC1和PC2都配置好ip&#xff0c;配置好之后&#xff0c;點擊右下角的應用 然后&#xff0c;在S2交換機&#xff08;S3700&#xff09;上做如下配置 #進入系統 <…

mvvm模式

MVVM&#xff08;Model-View-ViewModel&#xff09;模式是一種軟件設計模式&#xff0c;特別適用于構建用戶界面&#xff08;UI&#xff09;應用程序&#xff0c;尤其是使用WPF&#xff08;Windows Presentation Foundation&#xff09;、Silverlight和其他XAML技術的應用程序。…

【Redis】Redis十大類型

文章目錄 前言一、string字符串類型二、List列表類型三、 Hash表四、 Set集合五、 ZSet有序集合六、 GEO地理空間七、 HyperLogLog基數統計八、Bitmap位圖九、bitfield位域十、 Stream流10.1 隊列指令10.2 消費組指令10.3 ACK機制 前言 redis是k-v鍵值對進行存儲&#xff0c;k…

Mac上pyenv的安裝及使用

Mac上pyenv的安裝及使用 安裝 brew update brew install pyenv 報錯 git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallowgit -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow那就執行這2句 還報錯 git -C /…

【最經典的79個】軟件測試面試題(內含答案)提前備戰“金九銀十”

001.軟件的生命周期(prdctrm) 計劃階段(planning)-〉需求分析(requirement)-〉設計階段(design)-〉編碼(coding)->測試(testing)->運行與維護(running maintrnacne) 測試用例 用例編號 測試項目 測試標題 重要級別 預置條件 輸入數據 執行步驟 預期結果 0002.問&…

“論軟件維護方法及其應用”寫作框架,軟考高級論文,系統架構設計師論文

論文真題 軟件維護是指在軟件交付使用后&#xff0c;直至軟件被淘汰的整個時間范圍內&#xff0c;為了改正錯誤或滿足 新的需求而修改軟件的活動。在軟件系統運行過程中&#xff0c;軟件需要維護的原因是多種多樣的&#xff0c; 根據維護的原因不同&#xff0c;可以將軟件維護…

CVE-2024-34351 漏洞復現

CVE-2024-34351&#xff0c;由Next.js異步函數createRedirectRenderResult導致的SSRF。 影響版本&#xff1a;13.4.0< Next.js < 14.1.1 參考文章&#xff1a; Next.js Server-Side Request Forgery in Server Actions CVE-2024-34351 GitHub Advisory Database Gi…

數據庫Doris的手動分桶和自動分桶

在Doris中,分桶(Bucketing)是為了更好地管理和查詢數據,將數據分成多個小的邏輯單元。分桶可以通過手動或自動的方式進行配置,每種方式各有其特點和適用場景。 Doris 支持兩層的數據劃分。第一層是分區(Partition),支持 Range 和 List 的劃分方式。第二層是Bucket(Tab…

RK3568平臺開發系列講解(內存篇)Linux進程內存的消耗統計

??返回專欄總目錄 文章目錄 一、VSS(Virtual Set Size)二、RSS(Resident Set Size)三、PSS(Proportional Set Size)四、USS(Unique Set Size)五、其他工具Linux 提供了多種進程內存占用的度量指標, 它們反映了不同的內存使用特征: VSS 反映進程虛擬內存總需求, 包括未…

2.python條件語句與循環

1.概述 通過條件語句來判斷&#xff0c;條件成立執行某些代碼&#xff0c;條件不成立則不執行這些代碼 2.if語句 if條件&#xff1a;條件成立執行的代碼...... 下方代碼沒有縮進到if語句塊&#xff0c;所以和if條件無關if…else if條件&#xff1a;條件成立執行的代碼.....…

Nature Communications|柔性無感智能隱形眼鏡(柔性傳感/可穿戴電子/柔性電子)

南京大學徐飛(Fei Xu)、陸延青(Yanqing Lu)、陳燁(Ye Chen)和江蘇省人民醫院袁松濤(Songtao Yuan)團隊,在《Nature Communications》上發布了一篇題為“Frequency-encoded eye tracking smart contact lens for human–machine interaction”的論文。論文內容如下: 一、 摘…