笔者在刚接触QT信号与槽的时候很长的一段时间基本上都是用QT信号与槽最基础的形式去连接。发现大多数情况下也没啥问题。后来某次参加一次面试的时候被问到了这个问题,才有兴趣去了解了一下,用AI和查阅一下资料发现也是很官方很学术的去讲解,但是笔者是一个喜欢考虑所有场景、刨根问底把抽象的学术讲解形象化大白话理解的的一个人。比如讲到DirectConnection和QueuedConnection大部分只是官方的去讲一下,一个是通常相同线程对象使用,一个是在通常不同线程对象连接的时候使用(然后学术性的一堆啥啥),笔者就想如果调换一下场景呢,怎么使用,最终产生什么影响?原理是啥呢。

Qt::AutoConnection             自动判断(默认)
Qt::DirectConnection 直接调用
Qt::QueuedConnection 排队调用
Qt::BlockingQueuedConnection 阻塞排队
Qt::UniqueConnection 防止重复连接

QT信号与槽中一共如上的几个参数,但是最常用的其实是前三个。

一、Qt::AutoConnection 

sender 和 receiver 在同一线程 自动推导成------->DirectConnection;

不在同一线程:------->QueuedConnection;

两者有什么区别呢?往下看:

二、Qt::DirectConnection

直接调用槽函数。

啥意思呢?

常用情景:发送者和接收者同一个线程:

qDebug() << 1;

emit sig_test();

qDebug() << 2;

执行顺序:1----->槽函数----->2

QT会把槽函数立刻执行,而且槽函数也是在信号发送的线程执行的,再去执行信号下面的代码块。一般同一线程的都用这种情况。

情况二:发送者和接收者不在同一个线程呢。

//子类的定义
class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork()
    {
      qDebug() << "currentThread:" << QThread::currentThread();
      qDebug() << "object thread:" << this->thread();
    }
};


//主线程代码
QThread* t = new QThread;
Worker* w = new Worker;

w->moveToThread(t);

t->start();

//强制 DirectConnection
connect(this, &MainWindow::sig_test,
        w, &Worker::doWork,
        Qt::DirectConnection);


//主线程发信号
void main(){
qDebug() << "emit thread:" << QThread::currentThread();

emit sig_test();

qDebug()<<"信号后面的内容";
}

结果是:emit thread: 0xA (主线程)
           currentThread: 0xA (主线程) ← 关键点
           object thread: 0xB (子线程)

           信号后面的内容

这里槽函数在信号发生者的线程执行,主线程的事件队列一看,卧槽来新活了 插个队吧 把你这个dowrok往前排排 我当前的后面的代码有没有执行完我先不管了,立刻执行,再去管eimt后面的代码块!这是一种很危险的操作,子类所属线程在子线程,但是槽函数执行在了主线程。

三、Qt::QueuedConnection

常用情况:两个对象在不同线程进行通讯:

//主程序代码
qDebug() << "A";

emit sig_test();

qDebug() << "B";


//子线程的类的触发的槽函数
slot:

qDebug() << "S";

打印结果:A----->B------>S

实质上是 槽函数在接收者的线程执行,并且放在接收者线程的任务队列的末端,处理完了手头事情,再去调槽函数,而主线程发送完信号之后,马上执行下面的代码,不用管槽函数执行,大白话就是,主线程我自己干我自己的下面的事情,你槽函数爱在哪执行就在哪执行吧,执行管理槽函数的线程怎么去执行和我也没啥关系了

情况二:

两个对象在同线程进行通讯:



一、完整示例代码
#include <QCoreApplication>
#include <QDebug>
#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork()
    {
        qDebug() << "S (slot) thread:" << QThread::currentThread();
    }
};

class MainWindow : public QObject
{
    Q_OBJECT
signals:
    void sig_test();

public:
    void run()
    {
        qDebug() << "A (before emit) thread:" << QThread::currentThread();

        emit sig_test();

        qDebug() << "B (after emit) thread:" << QThread::currentThread();
    }
};


//main 函数(关键:同线程 + Queued)
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MainWindow m;
    Worker w;

    QObject::connect(&m, &MainWindow::sig_test,
                     &w, &Worker::doWork,
                     Qt::QueuedConnection);

    m.run();

    return a.exec();
}

最终打印顺序(重点)

A (before emit) thread: 0x主线程
B (after emit) thread: 0x主线程
S (slot) thread: 0x主线程

信号发出后,槽函数也在主线程执行,主线程的事件队列一看,妈耶,我的当前的run() 还没执行完呢,你这槽函数往后排排吧,于是先把run函数后面的代码执行,最后执行槽函数。

所以信号与槽的第五个参数主要关注这两个区别即可!

四、Qt::UniqueConnection(防重复连接)

防止重复 connect 同一信号槽

如果你写:

connect(a, &A::sig, b, &B::slot);
connect(a, &A::sig, b, &B::slot);
//slot 会执行两次


//加 UniqueConnection

connect(a, &A::sig,
        b, &B::slot,
        Qt::UniqueConnection);

重复 connect 会被忽略:slot 只执行一次Qt 内部会检查:

(sender, signal, receiver, slot)

是否已存在连接

五、Qt::BlockingQueuedConnection(阻塞队列连接)

跨线程调用 + 等待执行完成(同步阻塞)

emit → 投递到目标线程 → 等 slot 执行完 → 才返回;

connect(sender, &Sender::sig,
worker, &Worker::doWork,
Qt::BlockingQueuedConnection);

线程效果

假设:

  • A线程 emit
  • B线程 slot

那么:


A线程:
emit(sig)
  ↓(阻塞住)
等待B线程执行slot
  ↓
slot执行完
  ↓
A线程继续

特点

✔ 跨线程同步调用
✔ 自动线程切换
✔ emit 会卡住等待

非常重要的风险(重点)

❌ 禁止同线程使用

如果:


同线程 + BlockingQueuedConnection

会发生:

死锁

因为:

  • A线程发消息
  • A线程又等自己执行完
  • 但自己被阻塞了

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐