Qt信号槽的几种连接方式:AutoConnection/DirectConnection/QueuedConnection/BlockingQueuedConnection
目录
前言
Qt信号槽的5种连接方式
AutoConnection:自动连接
DirectConnection:直连
QueuedConnection:队列连接
BlockingQueuedConnection:阻塞队列连接
UniqueConnection:唯一连接
几种连接方式对比
一个包含5种连接方式的完整示例
总结
参考链接
非AI生成,觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。
前言
Qt的信号槽机制是Qt的最显著特征,信号和槽函数是通过connect函数连接的。由于信号的发送线程和接收线程可能不同,Qt5提供了5种信号槽连接方式供程序使用,深刻理解这几种连接方式的使用场景和区别,有助于我们编写更健壮的Qt程序,避免一些不必要的麻烦。Qt信号槽的5种连接方式
AutoConnection:自动连接
若信号发送者和接收者在相同线程,使用DirectConnection;若信号发送者和接收者在不同线程,使用QueueConnection。DirectConnection:直连
信号发送者和接收者在相同线程,发射信号后,立即在当前线程同步调用槽函数,如果槽函数还没执行完成,则会造成线程阻塞。
槽函数在发射信号的线程执行,多线程使用直连模式风险很高。
如果槽函数操作了UI,就相当于在子线程操作UI,结果就是程序随机崩溃或其他非常严重的问题,UI只能在主线程操作,子线程绝对不能操作UI,这是Qt的铁律。
多线程信号槽连接严禁使用直连方式。QueuedConnection:队列连接
多线程首选队列连接方式,槽函数在接收者所在线程执行,不阻塞发送者线程。
信号发射是“立刻完成的”,槽函数执行是“延后的、由接收者的事件循环调度的”。
Qt::QueuedConnection的信号发射只是向接收者线程投递一个事件,槽函数只有在接收者线程的事件循环轮询并处理该事件时才会执行。BlockingQueuedConnection:阻塞队列连接
多线程备选连接方式,阻塞的是“信号发送者所在的线程”,而不是接收者线程。
具体机制如下:
1.发送者线程阻塞:当发射信号时,发送者线程会把事件投递到接收者线程的事件队列,然后自身通过内部信号量(QSemaphore)进入阻塞等待状态,不会继续执行后面的代码。
2.接收者线程正常执行:接收者线程的事件循环照常取出该事件,在接收者所在线程执行槽函数。槽函数执行完毕后,内部会释放信号量通知发送者线程。
3.解除阻塞:接收者槽函数返回后,发送者线程被唤醒,从 emit处继续执行。
特别注意:必须跨线程,发送者和接收者绝对不能在同一个线程,否则发送者线程会等自己处理事件,导致死锁。
慎用场景:常用于需要跨线程同步结果的场景,但因容易引发死锁或界面卡顿(如子线程阻塞主线程),日常应优先使用普通的 Qt::QueuedConnection。UniqueConnection:唯一连接
Qt 信号槽机制中的特殊标志位,用于确保相同的发送者、信号、接收者和槽之间仅建立一次连接,重复调用connect会失败并返回false,从而防止重复触发和内存泄漏 。几种连接方式对比
一个包含5种连接方式的完整示例
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QThread> #include <QMutex> #include <QPointer> #include <QMessageBox> #include "Utility.h" #include "Worker.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_btnSetConnectionType_clicked(); private: Ui::MainWindow *ui; QThread* m_thread = nullptr; Worker* m_worker = nullptr; QPointer<QThread> m_thread_guard = nullptr; QPointer<Worker> m_worker_guard = nullptr; private: bool stop(); void startConnectionTest(); private slots: void onMessageReceived(ThreadType threadType,const QString& msg); void on_btnClear_clicked(); }; #endif // MAINWINDOW_H#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::startConnectionTest() { int itemIndex = ui->combConnType->currentIndex(); QString itemText = ui->combConnType->currentText(); itemText = QString("信号槽连接方式:%1").arg(itemText); Qt::ConnectionType connType = Qt::AutoConnection; switch (itemIndex) { case 0: connType = Qt::AutoConnection; break;//槽函数自动选择执行的线程 //槽函数在发射信号的线程执行,多线程使用直连模式风险很高 //如果槽函数操作了UI,就相当于在子线程操作UI,结果就是程序随机崩溃或其他非常严重的问题,UI只能在主线程操作,子线程绝对不能操作UI,这是Qt的铁律 //多线程信号槽连接严禁使用直连方式 case 1: connType = Qt::DirectConnection; break; //多线程首选队列连接方式,槽函数在接收者所在线程执行,不阻塞发送者线程 //信号发射是“立刻完成的”,槽函数执行是“延后的、由接收者的事件循环调度的” //Qt::QueuedConnection的信号发射只是向接收者线程投递一个事件,槽函数只有在接收者线程的事件循环轮询并处理该事件时才会执行。 case 2: connType = Qt::QueuedConnection; break; //多线程次选连接方式,阻塞的是“信号发送者所在的线程”,而不是接收者线程 //特别注意:必须跨线程,发送者和接收者绝对不能在同一个线程,否则发送者线程会等自己处理事件,导致死锁。 //慎用场景:常用于需要跨线程同步结果的场景,但因容易引发死锁或界面卡顿(如子线程阻塞主线程),日常应优先使用普通的 Qt::QueuedConnection /* 具体机制如下: 发送者线程阻塞:当发射信号时,发送者线程会把事件投递到接收者线程的事件队列,然后自身通过内部信号量(QSemaphore)进入阻塞等待状态,不会继续执行后面的代码。 接收者线程正常执行:接收者线程的事件循环照常取出该事件,在接收者所在线程执行槽函数。槽函数执行完毕后,内部会释放信号量通知发送者线程。 解除阻塞:接收者槽函数返回后,发送者线程被唤醒,从 emit处继续执行。 */ case 3: connType = Qt::BlockingQueuedConnection; break; case 4: connType = Qt::UniqueConnection; break; default:connType = Qt::AutoConnection;break; } if(1 == itemIndex){ QMessageBox::critical(this,"系统提示","多线程使用DirectConnection连接信号槽是高危行为,会导致程序闪退\n您头铁要试试看吗"); } onMessageReceived(ThreadType::UnKnown,itemText); m_thread = new QThread; m_worker = new Worker(nullptr,"Worker1",5,ThreadType::MoveToThread);//parent必须传nullptr,否则会违背Qt的线程亲和性规则,出现各种严重问题 connect(m_thread,&QThread::started,m_worker,&Worker::Start); connect(m_worker,&Worker::messageReceived,this,&MainWindow::onMessageReceived,connType); m_worker_guard = m_worker;//使用QPointer<Worker>防悬空指针误用 m_thread_guard = m_thread;//使用QPointer<QThread>防悬空指针误用 connect(m_worker,&Worker::finished,this,[=](){ if(m_worker_guard){ m_worker_guard->deleteLater(); QString msg = QString("%1 Worker对象释放成功").arg(Utility::getNowTimeStrWithMs()); onMessageReceived(ThreadType::UnKnown,msg); } if(m_thread_guard){ m_thread_guard->quit(); } }); connect(m_thread,&QThread::finished,this,[=](){ if(m_thread_guard){ m_thread_guard->deleteLater(); QString msg = QString("%1 QThread对象释放成功").arg(Utility::getNowTimeStrWithMs()); onMessageReceived(ThreadType::UnKnown,msg); } }); m_worker->moveToThread(m_thread); m_thread->start(); } void MainWindow::onMessageReceived(ThreadType threadType, const QString &msg) { static QMutex mutex; QMutexLocker<QMutex> locker(&mutex); ui->tbLog->appendPlainText(msg); } void MainWindow::on_btnSetConnectionType_clicked() { if(stop()){ startConnectionTest(); } } //不建议强制停止正在运行的线程,容易出现内存泄漏或其他问题 bool MainWindow::stop() { if(m_thread_guard){ if(m_thread_guard->isRunning()){ QMessageBox::information(this,"系统提示","线程正在执行,请等待线程执行完成"); return false; } m_thread_guard->quit(); m_thread_guard->wait(); } else{ qDebug()<<"QThread对象已经释放了,可以安全开启新线程了"; return true; } return false; } void MainWindow::on_btnClear_clicked() { ui->tbLog->clear(); }总结
如果能确定信号发送线程和接收线程,可以明确指定连接方式,大多数场景可以使用Qt::AutoConnection,Qt会自动帮您选择连接方式。参考链接
别再乱连了!Qt信号槽重复连接的坑,我用Qt::UniqueConnection帮你填平
深入解析 QObject::connect 的五种连接类型