(十)QT信号和槽的几种联接方式以及注意事项

本文详细解析了QT中的五种连接方式:自动、直接、队列、阻塞及唯一连接的特点与应用场景,通过实例对比不同连接方式下的信号与槽行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇我们介绍了QT的信号和槽,介绍connect的时候,我看可以看到connect函数是有第五个参数的,这第五个参数就是连接方式:

static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);

我们可以点击去看下连接方式的介绍,实际就是一个枚举类:

    enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };

从上到下依次是:自动连接、直接连接、队列连接、阻塞连接、唯一连接。其实QT的帮助文档也有介绍:
This enum describes the types of connection that can be used between signals and slots. In particular, it determines whether a particular signal is delivered to a slot immediately or queued for delivery at a later time.
这个枚举类描述了信号和槽之间的连接方式,特别是它决定了信号发送后槽函数是立刻响应还是排队等候响应。

直接连接方式:DirectConnection,顾名思义,就是信号发出后槽立刻响应,但是注意信号和槽要在同一个线程中,否则线程不安全。

排队连接方式:QueuedConnection,也非常直观的名字,就是信号发出后等待QT的调度(QT是有自己的事件循环的),排到他以后再执行槽函数。注意槽函数是在接收方的线程中执行的。也就是如果你的信号是在另外一个线程中发出的,那么槽函数就不和信号在同一个线程内这也是为啥QT要创造moveToThread()的线程方式,因为如果你在子线程内发出了信号,信号的连接方式选择自动连接(默认就是自动连接)或者队列连接,那么槽函数就会在主线程执行,那你写子线程的意义就为0。

写个例子看下队列连接和直接连接的区别吧:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QTime"
#include <Windows.h>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::DirectConnection);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test1,Qt::QueuedConnection);
}

MainWindow::~MainWindow()
{
    delete ui;
}

int MainWindow::slot_get_test()
{
    QDateTime nowtime = QDateTime::currentDateTime();
    Sleep(1000);
    QDateTime time = QDateTime::currentDateTime();
    ui->label->setText(QString("hello, i'm QT, now time ").append(nowtime.toString("hh:mm:ss.zzz ")).append("wait one second ").append(time.toString("hh:mm:ss.zzz")));
    return 0;
}

int MainWindow::slot_get_test1()
{
    Sleep(1000);
    QDateTime time = QDateTime::currentDateTime();
    ui->label_2->setText(QString("hello, i'm QTime ").append(time.toString("hh:mm:ss.zzz")));
    return 0;
}

void MainWindow::on_pushButton_clicked()
{
    emit signal_send_test();
}

“hello,i’m QT”是直连方式,"hello, i’m QTime"是队列连接,按照连接方式的描述,“hello,i’m QT”会优先执行,结果如图:
在这里插入图片描述

确实如我们预料,前后差了1秒后,直接连接立刻执行了,并且排在队列连接后等待事件循环。并且这个顺序和你connect的前后顺序无关,即使你把顺序更改,执行的结果也是一样的:

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::DirectConnection);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test1,Qt::QueuedConnection);

那么又有人问了,如果两个都是直连呢?QT如何执行,答案就是按照QT的任务调度,看哪个signal先发出

    emit signal_send_test1();
    emit signal_send_test();
    
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::DirectConnection);
    connect(this,&MainWindow::signal_send_test1,this,&MainWindow::slot_get_test1,Qt::DirectConnection);

在这里插入图片描述
那么如果发出的信号是相同的呢?答案是哪个connect先建立,和connect的顺序有关

emit signal_send_test();

connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test1,Qt::DirectConnection);
connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::DirectConnection);

在这里插入图片描述

阻塞连接:BlockingQueuedConnection,信号发出后,等待槽函数执行完毕再执行下去,其实就是用在线程中的。子线程中的信号发出后就处于等待状态,槽函数在槽函数所在的线程中执行完毕后,信号所在的子线程才会执行下去。QT特别强调,信号和槽函数一定不能在同一个线程中,否则会导致程序卡死

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test1,Qt::BlockingQueuedConnection);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::DirectConnection);

程序运行时会崩溃,报错如下:
Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is MainWindow(0x9c3273fa70), receiver is MainWindow(0x9c3273fa70)
在启动一个阻塞连接时发生了死锁:发送者是MainWindow,接受者是MainWindow
在这里插入图片描述
对于这种连接方式,我的推荐是:除非是线程必须,否则请不要随便触碰这个连接方式(虽然有点讳疾忌医的意思,但是QT很多时候,你不知道自己是不是放在同一个线程中了,一旦崩溃还挺麻烦的)

唯一连接:UniqueConnection,其实这是一个很迷的连接方式,它严格意义上说其实不是连接方式,它可以和前四个连接方式配合使用,它要求:1.相同信号名;2.相同对象;3.相同槽函数,如果满足这三个条件,你再次connect是不会成功的,connect只有第一次成功,也就是只会调用一次槽函数。
看下例子就明白了,为了表示槽函数执行次数,我引用了一个全局的count数字,看下它执行几次:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QTime"
#include <Windows.h>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , count(0)
{
    ui->setupUi(this);

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);
}

MainWindow::~MainWindow()
{
    delete ui;
}

int MainWindow::slot_get_test()
{
    count++;
    QDateTime nowtime = QDateTime::currentDateTime();
    Sleep(1000);
    QDateTime time = QDateTime::currentDateTime();
    ui->label->setText(QString("hello, i'm QT, now time ").append(nowtime.toString("hh:mm:ss.zzz ")).append("wait one second ").append(time.toString("hh:mm:ss.zzz ")).append(QString::number(count)));
    return 0;
}

void MainWindow::on_pushButton_clicked()
{
    emit signal_send_test();
}

在这里插入图片描述
可以看到count是2,说明我们调用的槽函数次数是两次,两次connect都成功了
那么我们修改下代码:

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::UniqueConnection);

注意下先后顺序哦,我是先建立的connect连接,再建立第二个connect时,我告诉QT这是一个唯一连接,那么第二个不会生效
运行结果如下:
在这里插入图片描述
connect只生效一次

那么肯定的,如果我们这样做,第三次connect没告诉QT这个是唯一连接,QT就会只让第二次建立不成功,第三次connect会成功,结果槽函数执行两次:

    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test,Qt::UniqueConnection);
    connect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);

在这里插入图片描述
默认连接方式:AutoConnection,也就是自动连接,根据connect函数就可以知道,connect默认是自动连接,除非你指定连接方式。这个是QT最常用的连接方式,如果信号和槽在同一个线程,那么QT会选用直接连接;如果信号和槽不在同一个线程,那么QT选用队列连接。至于何时选择呢?信号发出以后,也就是emit生效以后,QT会根据是否在同一个线程内自动选择。

信号和槽的一些说明:
1.一个信号可以对应多个槽函数
这个信号发出后,这些槽函数都会相应,调用顺序不可控
2.多个信号对应一个槽函数
只要有一个信号发出,这个槽函数都会相应
3.信号连接信号
一个信号传递给另外一个信号,另外一个信号再发给槽函数(如果有的话),和信号-槽没多大区别。一般的用处是为了减少编译时间,防止一个文件被多次引用导致的编译慢。(例如A文件引用了B,B文件引用了C,你可以让A文件emit信号给B,B文件emit信号给C,这样就可以间接调用C文件的槽函数,而不用A再次引用C)
4.取消信号和槽函数
一般是调用disconnect函数,disconnect函数只有四个参数,无需把连接方式传入。

disconnect(this,&MainWindow::signal_send_test,this,&MainWindow::slot_get_test);

总结下:
1.默认连接:如果信号和槽在同一个线程,那么QT会选用直接连接;如果信号和槽不在同一个线程,那么QT选用队列连接;
2.直接连接:信号发出后槽立刻响应,但是注意信号和槽要在同一个线程中,否则线程不安全
两个都是直连看哪个signal先发出
发出的信号相同看哪个connect先建立,和connect的顺序有关
3.队列连接:信号发出后等待QT的调度,排到他以后再执行槽函数。槽函数是在接收方的线程中执行的
4.阻塞连接:信号发出后,等待槽函数执行完毕再执行下去,信号和槽函数一定不能在同一个线程中,否则会导致程序卡死
5.唯一连接:如果是相同信号相同对象发给相同槽函数,再次connect是不会成功的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值