爲了可靠地獲得的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;
}
長的故事:
的文件是錯誤的。你可以移動QObject
父母到另一個線程。要做到這一點,您只需撥打moveToThread()
就可以移動QObject
層級的根,並且所有的孩子也會被移動(這是爲了確保父母和他們的孩子總是在同一個線程中)。我知道這是一個學術上的區別。只是在這裏徹底。當QObject
的thread()
不是== QThread::currentThread()
的moveToThread()
調用也可能失敗(即你只能推對象到,但另一個線程不拉一個)。
最後一句是lie-to-children。您可以拉的對象,如果它之前已經分離與任何線程(通過調用moveToThread(nullptr)
。
當線程親和力的變化,對象發送一個QEvent::ThreadChange
事件。
現在,您的問題是如何可靠地檢測到此舉發生了。答案是:這並不容易。最明顯的第一件事情,後moveToThread()
調用QObject::thread()
返回值比較的moveToThread()
的說法是不是一個好主意,因爲QObject::thread()
沒有(證明是)是線程安全的(參見the implementation)。
這是爲什麼?
只要moveToThread()
返回,移動到的線程可能已經開始執行「對象」,即。該對象的事件。作爲該處理的一部分,該對象可能被刪除。在這種情況下,在原始線程上調用QObject::thread()
將取消引用已刪除的數據。或者新線程將對象移交給另一個線程,在這種情況下,在原線程中調用thread()
時讀取成員變量將與在新線程中對moveToThread()
中的同一成員變量的寫入競爭。
底線:從原始線程訪問一個moveToThread()
ed對象是未定義的行爲。 不要這樣做。
唯一的出路就是使用ThreadChange
事件。在檢查完所有失敗案例之後發送該事件,但重要的是仍然從原始線程發送(參見the implementation;如果實際上沒有發生線程更改,則發送此類事件也是錯誤的)。
您可以通過繼承移動的對象並重新實現QObject::event()
或通過在要移動的對象上安裝事件過濾器來檢查事件。
事件過濾器方法更好,當然,因爲您可以將它用於任何QObject
,而不僅僅是您可以或想要子類化的那些。但是有一個問題:一旦事件被髮送,事件處理就切換到新的線程,所以事件過濾器對象將從兩個線程中敲定,這絕不是一個好主意。簡單的解決方案:讓事件過濾器移動對象的子項,然後它將隨之移動。另一方面,如果控制存儲的生命週期,那麼即使移動的對象在到達新線程時立即刪除,也可以獲得結果。長話短說:存儲需要是舊線程中變量的引用,而不是要移動的對象或事件過濾器的成員變量。然後,對存儲的所有訪問都來自原始線程,並且沒有比賽。
但是,但是...不是仍然不安全?是的,但只有當對象被移動再次到另一個線程。在這種情況下,事件過濾器將從第一個移動到的線程訪問存儲位置,並且將與來自始發線程的讀訪問競爭。簡單的解決方案:在發射一次後卸載事件過濾器。該實現留給讀者作爲練習:)
也許通過重載QThread? – 2015-01-01 12:06:42