2014-11-01 44 views
2

關於QObject::moveToThread()的文檔Qt5.3解釋說,如果對象有父項,moveToThread()方法可能會失敗。我如何在我的代碼中檢測到這種失敗?如何檢測Qt5中的QObject :: moveToThread()失敗?

我意識到只要確保我的對象先沒有父對象就足夠了,但作爲一種防禦性編程實踐,我想測試所有可能失敗的調用的返回值。

編輯:我想強調一些答案後,我完全知道我可以測試如果父母是0之前調用moveToThread。我正在尋找可能的方法來根據經驗確定moveToThread調用實際上已成功。

+0

也許通過重載QThread? – 2015-01-01 12:06:42

回答

4

爲了可靠地獲得的moveToThread()的結果,捕捉ThreadChange事件發生移動(通過重寫QObject::event()或安裝的事件過濾器)的對象,並存儲該事件是否已在參考被看見到本地變量:

static bool moveObjectToThread(QObject *o, QThread *t) { 
    class EventFilter : public QObject { 
     bool &result; 
    public: 
     explicit EventFilter(bool &result, QObject *parent = nullptr) 
      : QObject(parent), result(result) {} 
     bool eventFilter(QObject *, QEvent *e) override { 
      if (e->type() == QEvent::ThreadChange) 
       result = true; 
      return false; 
     } 
    }; 
    bool result = false; 
    if (o) { 
     o->installEventFilter(new EventFilter(result, o)); 
     o->moveToThread(t); 
    } 
    return result; 
} 

長的故事:

  1. 的文件是錯誤的。你可以移動QObject父母到另一個線程。要做到這一點,您只需撥打moveToThread()就可以移動QObject層級的,並且所有的孩子也會被移動(這是爲了確保父母和他們的孩子總是在同一個線程中)。我知道這是一個學術上的區別。只是在這裏徹底。當QObjectthread()不是== QThread::currentThread()

  2. moveToThread()調用也可能失敗(即你只能對象,但另一個線程不一個)。

  3. 最後一句是lie-to-children。您可以拉的對象,如果它之前已經分離與任何線程(通過調用moveToThread(nullptr)

  4. 當線程親和力的變化,對象發送一個QEvent::ThreadChange事件。

現在,您的問題是如何可靠地檢測到此舉發生了。答案是:這並不容易。最明顯的第一件事情,moveToThread()調用QObject::thread()返回值比較的moveToThread()的說法是不是一個好主意,因爲QObject::thread()沒有(證明是)是線程安全的(參見the implementation)。

這是爲什麼?

只要moveToThread()返回,移動到的線程可能已經開始執行「對象」,即。該對象的事件。作爲該處理的一部分,該對象可能被刪除。在這種情況下,在原始線程上調用QObject::thread()將取消引用已刪除的數據。或者新線程將對象移交給另一個線程,在這種情況下,在原線程中調用thread()時讀取成員變量將與在新線程中對moveToThread()中的同一成員變量的寫入競爭。

底線:從原始線程訪問一個moveToThread() ed對象是未定義的行爲。 不要這樣做。

唯一的出路就是使用ThreadChange事件。在檢查完所有失敗案例之後發送該事件,但重要的是仍然從原始線程發送(參見the implementation;如果實際上沒有發生線程更改,則發送此類事件也是錯誤的)。

您可以通過繼承移動的對象並重新實現QObject::event()或通過在要移動的對象上安裝事件過濾器來檢查事件。

事件過濾器方法更好,當然,因爲您可以將它用於任何QObject,而不僅僅是您可以或想要子類化的那些。但是有一個問題:一旦事件被髮送,事件處理就切換到新的線程,所以事件過濾器對象將從兩個線程中敲定,這絕不是一個好主意。簡單的解決方案:讓事件過濾器移動對象的子項,然後它將隨之移動。另一方面,如果控制存儲的生命週期,那麼即使移動的對象在到達新線程時立即刪除,也可以獲得結果。長話短說:存儲需要是舊線程中變量的引用,而不是要移動的對象或事件過濾器的成員變量。然後,對存儲的所有訪問都來自原始線程,並且沒有比賽。

但是,但是...不是仍然不安全?是的,但只有當對象被移動再次到另一個線程。在這種情況下,事件過濾器將從第一個移動到的線程訪問存儲位置,並且將與來自始發線程的讀訪問競爭。簡單的解決方案:在發射一次後卸載事件過濾器。該實現留給讀者作爲練習:)

+0

我認爲你在某些地方錯了。我不認爲文件是錯誤的,而是你看待錯誤的觀點。您不能將具有父級的對象移動到另一個線程。在你的例子中,你正在移動父對象,它沒有父對象,也沒有父對象。換句話說,你正在移動整個對象的層次結構,其範圍只有一個,我不認爲它是你想要的。你說的是將一個較少的父對象移動到另一個線程也會移動它的子對象,但這是另一回事。 – Iuliu 2015-01-02 01:48:23

+0

「這是爲了確保父母和他們的孩子總是在同一線索上」 - 父母和他們的孩子不能處於不同的線索。 – Iuliu 2015-01-02 01:48:59

+0

'ThreadChanged'是在前一個線程中發送到此對象的最後一個事件。這意味着當收到'ThreadChanged'時,對象仍然在前一個線程中。那麼如何確保在發送'ThreadChanged'後,對象不會移動?... – 2015-01-02 12:02:18

1

QObject::moveToThread僅當它有父項時才失敗。如果它的父母是NULL那麼你可以移動它,否則你不能。

編輯:

你可以做的是你可以檢查對象的線程affinity通過調用QObject::thread和檢查,如果它真的改變了它的親和力稱爲moveToThread後。

QThread *pThread = new QThread; 

QObject *pObject = new QObject; 

{ 
    QMutexLocker locker(&mutex); 

    pObject->moveToThread(pThread); 

    if(pObject->thread() != pThread) 
    { 
     qDebug() << "moveToThread failed."; 
    } 
} 
+0

正如我在問題中所述,我意識到這一點。我正在尋找如何測試之後是否失敗。 – 2014-11-01 20:19:12

+0

我知道如果有父母,我可以檢查FIRST,然後PRESUME在父母爲0時工作。然而,任何多線程軟件都會發生瘋狂的事情,並且感覺充分偏執,我想要經驗證據證明它實際上並沒有失敗。 – 2014-11-01 20:23:26

+0

我聽到你的聲音,但是在我作爲開發人員的幾年中,我遇到了我的那些分歧和意想不到的錯誤,其中大多數錯誤只是通過檢查返回值和驗證最基本的假設來避免。我已經將此發展成爲一種實踐,它對我很好。此外,我認爲moveToThread()在Qt5中是非常關鍵的功能。由於Qt的基本架構,確實知道代碼實際上在線程中運行是一件大事,因爲替代方案是它會(意外地)繼續在主線程中運行。 – 2014-11-01 20:41:58