綫程和 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 範例 Blocking Fortune Client example .

一般而言,創建 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 () signal. Calling deleteLater () won't work either. (These restrictions apply to the main thread as well.)

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

支持事件過濾器的所有綫程,具有監視對象必須活在如被監視對象的同一綫程中的限定。同樣, QCoreApplication::sendEvent () (不像 postEvent ()) can only be used to dispatch events to objects living in the thread from which the function is called.

從其它綫程訪問 QObject 子類

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

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

像其它對象, QThread objects live in the thread where the object was created – not 在創建綫程中,當 QThread::run () 被調用。提供槽通常不安全在 QThread 子類,除非采用互斥保護成員變量。

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

信號和槽跨綫程

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

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

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

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

可以指定連接類型,通過把額外自變量傳遞給 connect (). Be aware that using direct connections when the sender and receiver live in different threads is unsafe if an event loop is running in the receiver's thread, for the same reason that calling any function on an object living in another thread is unsafe.

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

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

同樣, Blocking Fortune Client example 使用單獨綫程與 TCP 服務器進行異步通信。