綫程和 QObject

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

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

QObject 重入

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

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

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

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

每綫程事件循環

Each thread can have its own event loop. The initial thread starts its event loops using QCoreApplication::exec (); other threads can start an event loop using QThread::exec ()。像 QCoreApplication , QThread 提供 exit (int) 函數和 quit() 槽。

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

Threads, objects, and event loops

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

Note that for QObjects that are created before QApplication , QObject::thread () returns zero. This means that the main thread will only handle posted events for these objects; other event processing is not done at all for objects with no thread. Use the QObject::moveToThread () function to change the thread affinity for an object and its children (the object cannot be moved if it has a parent).

調用 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 支持這些信號/槽連接類型:

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

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

  • 唯一連接 The behavior is the same as the Auto Connection, but the connection is made only if it does not duplicate an existing connection. i.e., if the same signal is already connected to the same slot for the same pair of objects, then the connection is not made and connect() returns false.

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

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

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

同樣, 阻塞 Fortune 客戶端 example uses a separate thread for communicating with a TCP server asynchronously.