Qt单线程中一个信号绑定多个槽,槽调用时序探索

  信号(signal)与槽(slot)是Qt特有的机制,它可以让控件间的通信变的很方便。你也可以很轻易地使用一个signal绑定多个slot,本文谈一下一个signal绑定多个slot时,slot的执行顺序。

connect函数

 signal和slot通过connect连接,继承于QObject的类可以使用这种机制。

[static] QMetaObject::Connection QObject::connect(
	const QObject *sender, const char *signal,
 	const QObject *receiver, const char *method,
  	Qt::ConnectionType type = Qt::AutoConnection)

 它将sender的signal与receiver的method绑定,当你调用emit signal时,它便会调用触发receiver的method。不过它还有第五个参数,Qt::ConnectionType连接类型,你可能没有了解过它。

信号与槽的连接类型

 Qt::ConnectionType连接类型由一个enum(枚举)描述。

	enum Qt::ConnectionType

 它确定你所连接的signal在触发时,是立即调用slot函数,还是进入队列等待后调用。它的具体描述如下:


constant value Description
Qt::AutoConnection 0 (默认)如果receiver位于发送signal的线程中,则使用Qt::DirectConnection,否则使用Qt::QueuedConnection。具体类型在信号发出时判定。
Qt::DirectConnection 1 当signal发出时,slot立即被调用。slot在signal线程中执行
Qt::QueuedConnection 2 当控制返回到receiver的线程中时,slot被调用。slot执行在receiver的线程中。
Qt::BlockingQueuedConnection 3 与Qt::QueuedConnection相同,只是发出signal的线程堵塞,知道插槽返回。如果receiver位于发送signal线程,则不能使用此连接,否则应用程序死锁。
Qt::UniqueConnection 0x80 (按位或)组合模式 (Qt4.6后引入)

 队列类型的连接,参数必须是Qt元对象系统所知的类型,因为Qt需要拷贝参数以将它们存储在后台事件中。如果你使用未知类型,将会报以下错误。

	QObject::connect: Cannot queue arguments of type 'MyType'

测试代码

 由于使用场景需要,我这里只讨论单线程中,使用单signal触发多个slot能不能产生多线程的效果。

Qt::AutoConnection

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
signals:
    void sig();

private slots:
    void on_pushButton_clicked();
    void slotA();
    void slotB();

private:
    Ui::MainWindow *ui;
    int m_nT;
};

#endif // MAINWINDOW_H

// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

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

    m_nT = 0;
    connect(this, SIGNAL(sig()), SLOT(slotA()), Qt::AutoConnection);
    connect(this, SIGNAL(sig()), SLOT(slotB()), Qt::AutoConnection);
}

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

void MainWindow::on_pushButton_clicked()
{
    emit sig();
   //QTimer::singleShot(0, this, SLOT(slotA1()));
   //QTimer::singleShot(0, this, SLOT(slotA2()));
    printf("cccc");
}

void MainWindow::slotA()
{

    for (int i = 0; i < 10; i++)
        printf("A%d", m_nT++);


}
void MainWindow::slotB()
{
    for (int i = 0; i < 10; i++)
        printf("B%d", m_nT++);

}

 运行后,点击按钮发送sigA(),多次测试,记录观察结果。
 发现结果始终如下,

	A0A1A2A3A4A5A6A7A8A9B10B11B12B13B14B15B16B17B18B19cccc

 我们可以得到两个slot函数不存在交错运行。结合文档的描述,这边receiver在sender线程中,则使用Qt::DirectConnection,当signal发出时,slot立即被调用。slot在signal线程中执行。所以该情况下,slotA和slotB都运行在一个线程(主线程)中,它们是顺序执行的(单线程)。sig发送,立即触发slotA,执行完slotA,然后触发slotB,再执行printf(“cccc”)。

Qt::QueuedConnection

 若把上面代码中slotA和slotB的连接类型都改为Qt::QueuedConnection。

	Qt::QueuedConnection,当控制返回到receiver的线程中时,slot被调用。slot执行在receiver的线程中。

 多次测试,结果如下:

	ccccA0A1A2A3A4A5A6A7A8A9B10B11B12B13B14B15B16B17B18B19

 同样可以看到,两个slot函数是不存在交错运行的。但是这次printf(“cccc”)打印在slotA和slotB之前了。可以推测,发送sig的时候,主线程的当前函数还在执行,此时slotA和slotB一起进入队列等待主线程当前函数执行完毕,再依次执行。这与DirectConnection连接的立即执行有所不同。

1.QueuedConnection 2.AutoConnection

 多次测试,结果如下:

	B0B1B2B3B4B5B6B7B8B9ccccA10A11A12A13A14A15A16A17A18A19

 可以得到QueuedConnection的优先级是在主线程之后的,等到主线程当前函数执行完,才会执行对应的slot函数。

1.AutoConnection 2.QueuedConnection

 多次测试,结果如下:

	A0A1A2A3A4A5A6A7A8A9ccccB10B11B12B13B14B15B16B17B18B19

结论

 单线程中使用单signal绑定多slot,触发slot函数执行也是单线程执行,其执行有先后顺序。
 单线程中DirectConnection对应的slot函数会被立刻调用,优先级高于当前执行函数,QueuedConnection对应的slot函数优先级低于当前执行函数(回到接收对象的事件循环中时再进行调用)。
Note:后者具体原因待验证

发布了60 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/103164458
今日推荐