2012-05-24 69 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; 
} 

保持你的方法体的其余部分是。