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:後者具體原因待驗證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章