2012-05-24 70 views
3

我遇到以下問題:我們的主應用程序使用Qt工具包來顯示窗口和用戶交互。然而,我們應用程序的很大一部分對GUI部分一無所知。我現在創建了以下設計:在多線程的Qt應用程序中處理升壓信號

  • 有一個單例類可請求呈現給定對象(OpenSceneGraph的節點;但這是無關的問題)
  • 呈現請求使單以發射信號
  • 有一個在主窗口類插槽(其中使用Qt)來處理渲染對象
  • 目前,該槽只創建一個新的文本編輯窗口小部件,並將其放置在主窗口的QMdiArea

但是,當我嘗試創建新的小部件時,應用程序不可避免地會崩潰。錯誤消息區域:

QObject::setParent: Cannot set parent, new parent is in a different thread 
[xcb] Unknown request in queue while dequeuing 
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called 
[xcb] Aborting, sorry about that. 
myApplication: ../../src/xcb_io.c:178: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed. 
Aborted 

在細讀了stackoverflow後,我發現了類似的問題(它不適用於這種情況)。很顯然,當我在另一個線程中更改主窗口中的某些內容時,Qt並不喜歡它。但是,我沒有自覺創建新的主題,我認爲單(這是在主函數QApplication()呼叫後立即創建)同一個線程爲Qt的。顯然,我錯了。

這裏是一個小例子,顯示我正在做的事情(我已經提取代碼的相關部分,這樣的例子並不完全功能):

class Object 
{ 
public: 
}; 

class Singleton 
{ 
public: 
    typedef boost::signals2::signal<void (Object*)> signalShowObject; 
    signalShowObject _showObject; 
}; 

class MainWindow : public QMainWindow 
{ 
public: 
    MainWindow() 
    { 
    Singleton::getInstance()->_showObject.connect(boost::bind(&MainWindow::showObject, this, _1)); 

    // Set up MDI area etc. 
    } 

private: 
    QMdiArea* _mdiArea; 

    void showObject(Object* object) 
    { 
    // Creating a new subwindow here causes the crash. The `object` pointer is 
    // not used and has just been included because it models my real problem 
    // better. 
    _mdiArea->addSubWindow(new QTextEdit())->show(); 
    } 
}; 

我試圖解決這個問題有一直很笨拙:

  • 我在MainWindow類創建了一個新的Qt 信號具有相同簽名的加速信號
  • 在SL OT處理該升壓信號,我發射新Qt信號,使指針移到
  • 我現在創建接收指針新的Qt

當我在新的插槽打開一個新的窗口,一切正常。但是,這讓我覺得很笨拙。我是否需要級聯全部提升這樣的信號還是有更好的方法?

+0

只是要清楚。每個gui操作必須在QApplication線程中完成。如果你想檢查你的方法是否在正確的線程調用中使用qDebug()<< thread()<< QThread :: currentThread();告訴我們showObject成員中該調試的輸出是什麼。 –

+0

@KamilKlimek我在我的「真實」應用程序中獲得了線程ID,它們確實不同。 – Gnosophilon

+0

你可以調用插槽,然後用QMetaObject :: invokeMethod –

回答

1

我認爲令人困惑的是,從呈現請求調用單例是由任何線程產生請求。單例將返回一個唯一的對象,但它發送的信號仍然在請求線程的上下文中。爲了實際處理這個信號並在主線程中創建UI對象,必須做某些事情來顯式地導致或允許線程上下文切換到主UI線程。

,你在這個序列implictly這樣做,你形容:

•我創建了MainWindow類新的Qt信號具有相同 簽名升高信號

•在插槽處理該升壓信號,我發射新的Qt信號, 使指針移到

•我現在創建接收指針新的Qt槽

Qt信號和插槽自動排隊跨線程信號(注1)。所以處理Boost信號的插槽仍在請求線程中。然後它發出Qt信號。 Qt檢測到信號的接收者在主線程中(注2),但發送者在請求者線程中,並且將信號排隊。當主線程中的主要Qt事件循環將該排隊事件從事件列表中拉出時,它會自動重新發出信號,但現在它位於主線程上下文中,並且允許UI操作。

注1 - 除非在connect()調用中明確覆蓋此行爲 - 請參閱文檔Qt :: ConnectionType

注2 - 實際上,接收器的QObject由主線程擁有。每個QObject都保留創建它的線程上下文的線程ID。

我希望這可以幫助解釋線程發生了什麼。你的解決方案很好,但是@tmpearce建議將它包裝在適配器中可能會很方便。

+0

感謝您的澄清! – Gnosophilon

0

定義showObject作爲插槽和小公式添加到它的身體:

if(QThread::currentThread() != thread()) 
{ 
    bool ok = QMetaObject::invokeMethod(this, "showObject", Qt::QueuedConnection, Q_ARG(QObject *, object)); 

    if(! ok) 
     qDebug() << "Couldn't invoke method"; 

    return; 
} 

保持你的方法體的其餘部分是。