当前位置: 首页 > news >正文

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 的五种连接类型
http://www.jsqmd.com/news/1138359/

相关文章:

  • 【HarmonyOS学习笔记】2026-07-06 | 页面与数据5:实现页面跳转
  • Spring Boot 3 + Vue 3 + MySQL MES生产制造执行系统源码前后端分离实战
  • Objective-C 之 Foundation框架 详解
  • 06-高级模式与实战项目——09. 单元测试 - Jest
  • 储能沙盘模型BMS功能模拟控制系统设计与实现:电压监测、均衡管理与故障保护的STM32方案
  • npx --yes localtunnel --port 3000不安装任何依赖的情况下将端口暴露在公开url上
  • Scikit-learn 1.5.0 实战:5行代码构建KNN分类器,准确率超95%(附数据集)
  • Ubuntu 22.04 + CUDA 12.1 深度学习环境配置:3步完成PyTorch 2.0与TensorFlow 2.10共存
  • EhViewer安全架构深度解析:多层次防护机制的技术实现
  • Windows 11 安装 Claude Code:配置国产模型(DeepSeek)、无需解决网络问题
  • StegaStamp 2019 CVPR 实战复现:PyTorch 1.13 环境配置与 100 位消息嵌入测试
  • 2026年国产大模型API价格横评:DeepSeek/Kimi/GLM/Qwen哪家最便宜?附迁移避坑指南
  • 小黄鸭(Lossless Scaling)ai补帧神器配置方法与安装教程
  • CTF ECDH密钥协商漏洞攻击 Writeup
  • U-Net 跳跃连接深度解析:Concat 与 Add 操作的 3 种实现与性能影响
  • 【单片机毕业设计】基于 STM32 的智能护眼台灯监测控制系统设计,基于单片机的坐姿矫正与久坐提醒智能台灯设计(018401)
  • 乐达,乐运助手app官方下载
  • 2026 电商 AI 图文 + 动态素材完整接单市场价(闲鱼 / 小红书 / 淘宝美工真实行情)
  • Excel复选框本质解析:布尔逻辑驱动的数据交互设计
  • 给 AI 用的代码索引器-技术篇
  • Nacos漏洞利用工具V3.0.5:安全评估实践与工程化实现
  • 嵌入式Linux网络通信知识框架-以太网、WiFi、4G与5G
  • 岗位薪资范围太宽不知道值多少?3个定位方法
  • UI自动化性能优化全链路实战:从脚本编码到分布式架构
  • Novel-Downloader:200+网站智能解析的小说下载神器
  • 德州中央空调维保怎么选?本地服务避坑指南
  • 从分立到集成:对比4款移动电源SOC与ME4057+MCU方案的成本与PCB面积
  • 影刀RPA新手教程:知乎自动化完全指南问题监控与回答采集实战
  • RAG 多路召回:让检索命中率从 60% 干到 85% 的 4 层优化
  • L 型 / 三折 / 四折 LED 异形大屏裸眼 3D 动画制作有哪些硬性技术要求?行业标准与避坑全解