綫程和 QObject

QThread 繼承 QObject 。它發射指示綫程啓動 (或執行完成) 的信號,且還提供瞭幾個槽。

更有趣的是 QObject 可以用於多綫程,發射援引其它綫程槽的信號,並把事件張貼給 "存活" 於其它綫程中的對象。這是可能的,因為每個綫程都允許擁有它自己的事件循環。

QObject 重入

QObject 可重入。它的大多數非 GUI (圖形用戶界麵) 子類,譬如 QTimer , QTcpSocket , QUdpSocket and QProcess ,也可重入,使之可能從多綫程同時使用這些類。注意,設計是從單綫程創建和使用這些類;在某個綫程中創建對象並從另一綫程調用其函數,不能保證能工作。要注意存在 3 個約束:

  • 子級對於 QObject 必須始終在創建父級的綫程中創建。 這隱含,除其它事情外,從不應該傳遞 QThread 對象 ( this ) 作為在綫程中創建對象的父級 (由於 QThread 對象自身是在另一綫程中創建的)。
  • 事件驅動對象隻可以用於單綫程。 具體來說,這適用於 計時器機製 網絡模塊 。例如,無法啓動計時器 (或連接套接字) 當所在的綫程不是 對象的綫程 .
  • 必須確保刪除綫程中創建的所有對象,先於刪除 QThread . 可以輕鬆做到這,通過創建堆棧對象在 run() 實現。

盡管 QObject 是可重入 GUI 類,顯而易見 QWidget 及其所有子類,都不可重入。隻可以從主綫程使用它們。如前所述, QCoreApplication::exec () 還必須從該綫程調用。

在實踐中,不可能在主綫程外的其它綫程中使用 GUI (圖形用戶界麵) 類,通過把耗時操作放入單獨工作者綫程,並在工作綫程完成時在主綫程屏幕中顯示結果,可輕鬆解決。此實現方式可以用於 Mandelbrot 範例 阻塞 Fortune 客戶端範例 .

一般而言,創建 QObject 先於 QApplication 不支持,且退齣時可能導緻奇怪崩潰,從屬平颱。這意味著靜態實例 QObject 也不支持。結構閤理的單 (或多) 綫程應用程序應該使 QApplication 被首先創造,且最後銷毀 QObject .

每綫程事件循環

每個綫程都可以擁有它自己的事件循環。初始綫程啓動其事件循環是使用 QCoreApplication::exec (),或對於單對話框 GUI (圖形用戶界麵) 應用程序,有時是 QDialog::exec ()。其它綫程啓動事件循環可以使用 QThread::exec ()。像 QCoreApplication , QThread 提供 exit (int) 函數和 quit() 槽。

綫程中的事件循環使之可能對要使用某些非 GUI Qt 類的綫程,要求存在事件循環 (譬如 QTimer , QTcpSocket ,和 QProcess )。還使之可能把來自任何綫程的信號,連接到特定綫程槽。闡述這的更多細節在 信號和槽跨綫程 以下章節。

Threads, objects, and event loops

A QObject 實例據稱是 live 在創建它的綫程中。此對象的事件由該綫程的事件循環分派。綫程對於 QObject 存活的獲得是使用 QObject::thread ().

The QObject::moveToThread () 函數改變對象,及其子級的綫程親緣關係 (對象無法移動,若它擁有父級)。

調用 delete QObject 從綫程而不是某個綫程其 owns 對象 (或以其它方式訪問對象) 是不安全的,除非保證對象在那刻不處理事件。使用 QObject::deleteLater () 代替,和 DeferredDelete 事件將被張貼,最終將拾取對象綫程的事件循環。默認情況下,綫程 owns a QObject 是綫程 creates the QObject ,但不後於 QObject::moveToThread () 被調用。

若沒有事件循環在運行,就不會將事件交付給對象。例如,若創建 QTimer 對象在綫程,但從不調用 exec() QTimer 將從不發射其 timeout() 信號。調用 deleteLater() 也不會工作 (這些限定也適用於主綫程)。

可以在任何時間手動將事件張貼給任何綫程中的任何對象,使用綫程安全函數 QCoreApplication::postEvent ()。將通過創建對象綫程的事件循環,自動分派事件。

支持事件過濾器的所有綫程,具有監視對象必須活在如被監視對象的同一綫程中的限定。同樣, QCoreApplication::sendEvent () (不像 postEvent() ) 隻可以用於把事件分派給活在從那裏調用函數的綫程中的對象。

從其它綫程訪問 QObject 子類

QObject 及其所有子類都不是綫程安全的。這包括整個事件交付係統。它很重要,記住事件循環可能把事件交付給 QObject 子類,當從另一綫程訪問對象時。

若正調用函數在 QObject 子類未活在當前綫程中且對象可能接收事件,就必須保護所有訪問對 QObject 子類的內部數據按互斥;否則,可能經曆崩潰 (或其它不期望行為)。

像其它對象, QThread 對象活在創建對象的綫程中 -- not 在創建綫程中,當 QThread::run () 被調用。提供槽通常不安全在 QThread 子類,除非采用互斥保護成員變量。

另一方麵,可以安全地發射信號從 QThread::run () 實現,因為信號發齣是綫程安全的。

信號和槽跨綫程

Qt 支持這些信號/槽連接類型:

  • 自動連接 (默認) 若在擁有親緣關係的接收對象的綫程中發射信號,那麼行為如同直接連接。否則,行為如同隊列連接。
  • 直接連接 立即援引槽,當發射信號時。在發射器綫程中執行槽,不必是接收者綫程。
  • 隊列連接 槽被援引,當控製返迴給接收者綫程的事件循環時。在接收者綫程中執行槽。
  • 阻塞隊列連接 如同隊列連接援引槽,除當前綫程阻塞,直到槽返迴外。

    注意: 使用這種類型連接同一綫程中的對象,會導緻死鎖。

  • 唯一連接 行為如同 "自動連接",但纔做齣連接,若它不復製現有連接時。即,若同一信號已連接到用於同一對對象的同一槽,那麼不會做齣連接且 connect() 返迴 false .

可以指定連接類型,通過把額外自變量傳遞給 connect() 。要意識到,當發送者和接收者活在不同綫程中時 (若事件循環運行在接收者綫程中) 使用直接連接不安全,齣於相同原因,調用活在另一綫程中對象的任何函數也不安全。

QObject::connect () 本身是綫程安全的。

The Mandelbrot 範例 使用 "隊列連接" 進行通信,在工作者綫程和主綫程之間。為避免凍結主綫程的事件循環 (並因此,凍結應用程序的用戶界麵),所有 Mandelbrot 分形計算都是在單獨工作者綫程中完成。綫程發射信號,當分形渲染完成時。

同樣, 阻塞 Fortune 客戶端範例 使用單獨綫程與 TCP 服務器進行異步通信。