Qt雜談6.淺談信號槽那些事

1 引言

Qt信號槽是一大特色,介紹它的文章也數不勝數,爲啥還要說呢,主要還是想從實現原理作爲切入點,談談一個信號發射到槽函數執行所經歷的大致流程,從宏觀角度進行一個簡單梳理,相比於一般的文章稍微深入一點點吧,畢竟水平有限,希望能幫到一些有一定Qt基礎的人。

2 信號槽執行流程

這裏主要分析信號槽隊列連接方式,比較有代表性一點。

2.1 建立連接

首先,使用QObject::connect()函數建立信號與槽的連接,這些信號槽的連接信息將以 QObjectPrivate::Connection 對象的形式存儲在 ConnectionList(QList<QObjectPrivate::Connection>) 列表中。每一個 Connection 對象包含了信號發送者對象的指針、信號的索引(對應於發送者信號列表中的位置)、槽的接收者對象的指針、槽的方法信息、連接的類型、額外的連接標誌或者參數。

連接的類型一般有三種:
直接連接(DirectConnection):槽函數會直接在信號發射的那個線程上立即執行。
隊列連接(QueuedConnection):如果信號和槽屬於不同的線程,槽函數的執行將會在目標槽所屬對象的事件循環中異步執行。爲了達到這個目的,Qt 會封裝一個 QMetaCallEvent 事件。
阻塞隊列連接(BlockingQueuedConnection):與隊列連接類似,但是發射信號的線程會阻塞等待槽函數執行完畢。

其中,ConnectionList 是一個鏈表數據結構,它保存了與一個特定信號相關聯的所有連接,在內存中動態分配,通常在對象被創建時初始化。如何關聯上呢?QObject 類有一個指向 QObjectPrivate 類型的私有數據指針(d_ptr),QObjectPrivate 包含一個或多個 ConnectionList 實例,需要注意的是每一個信號對應一個 ConnectionList。

2.2 發射信號

當信號被髮射時,moc生成的QMetaObject::activate函數會被調用,它會負責查找所有與信號鏈接的槽,並依次調用每個槽。

  1. 首先通過 QObjectPrivate 獲取到所有的信號槽連接信息;
  2. 通過發射信號的索引,activate() 函數可以確定哪些 Connection 對象是與當前發射的信號相關的;
  3. activate() 函數遍歷與信號相關的所有 Connection 對象。對於每個 Connection,它會根據連接時的類型來決定是直接調用槽函數,還是將調用事件(QMetaCallEvent)推送到指定接收者的事件隊列中;
  4. 如果槽是在相同線程被直接連接的,那麼 QObject::activate() 會直接調用該槽函數。如果是跨線程連接的方式,事件將被排隊,在接收者對象所處線程的事件循環中被處理,隨後調用對應的槽函數;
  5. 對於每個槽函數的調用,activate() 會傳遞正確的參數,這是通過從信號發射時傳遞的參數列表中複製參數實現的。

2.3 執行槽函數

前面提到,在信號發射時,Qt會檢查這些連接並確定如何調用關聯的槽函數。如果連接是 Qt::DirectConnection 並且信號與槽位於同一線程,那麼槽會直接被調用,如果連接是 Qt::QueuedConnection 或者信號與槽位於不同的線程,Qt 則會創建一個 QMetaCallEvent。

  1. 封裝事件(Event Wrapping):
    如果連接類型是 Qt::QueuedConnection 或 Qt::BlockingQueuedConnection,並且發射信號的線程與接收槽的線程不同,Qt會創建一個特殊的事件對象 QMetaCallEvent,它包含了調用槽函數所需的所有信息,包括接收者的槽函數指針、參數值的拷貝等。
  2. 事件投遞(Event Posting):
    生成的事件被投遞到接收對象所屬線程的事件隊列中。Qt使用QCoreApplication::postEvent()函數在應用程序的主事件循環中投遞事件,在這個過程中,發射信號的線程不會阻塞等待(除非是 Qt::BlockingQueuedConnection 類型,這時會等待槽方法執行完畢)。
  3. 事件處理(Event Processing):
    在接收者所屬線程,線程的事件循環將持續運行等待事件,接收者線程的事件循環收到 QMetaCallEvent 後,會根據事件中封裝的信息,調用目標對象的 qt_metacall 函數執行相應的槽方法。
    qt_metacall 是 Qt 框架中的一個特殊成員函數,它是 QObject 類的一部分,它允許動態調用槽和操作屬性,由 Q_OBJECT 宏定義,是元對象系統的核心,它允許實現反射和對象間的通信,而 QMetaCallEvent 是一種用於線程間槽調用的事件。這兩者協同工作,使得 Qt 的信號和槽機制可以跨線程安全地操作。
  4. 槽函數執行(Slot Invocation):
    在 qt_metacall 函數中,根據事件的信息,使用反射機制調用具體的槽函數,並將參數傳遞給它。這樣,即便是跨線程,槽函數也可以在接收者所在線程的上下文中被正確執行。

3 總結

很多東西被隱藏在Qt的元對象系統內部,通常開發者不必直接訪問或操作這些內部數據結構,但是如果想理解得更加深入,還是需要對實現原理做一定的瞭解。好了,有不對的地方,還請大家不吝賜教。

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