繁体   English   中英

使用多个线程在Qt应用程序中处理增强信号

[英]Handling a boost signal in Qt application with multiple threads

我有以下问题:我们的主要应用程序使用Qt工具包显示窗口和用户交互。 然而,我们的大部分应用程序都不了解GUI部分。 我现在创建了以下设计:

  • 有一个单例类可以请求呈现给定对象(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()之后在main函数中创建) 应该与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信号,其信号与Boost信号相同
  • 在处理Boost信号的插槽中,我发出新的Qt信号,将指针移过
  • 我现在创建了一个接收指针的新Qt插槽

当我在新插槽中打开一个新窗口时,一切正常。 然而,这让我非常笨拙。 我是否必须像这样级联 所有 Boost信号,还是有更好的方法?

我认为令人困惑的是,渲染请求中对单例的调用是从生成请求的任何线程中产生的。 单例将返回一个唯一的对象,但它发送的信号仍然在请求线程的上下文中。 必须做一些事情才能显式地导致或允许线程上下文切换到主UI线程,以便实际处理此信号并在主线程中创建UI对象。

你正在按照你描述的顺序暗示​​这样做:

•我在MainWindow类中创建了一个新的Qt信号,其信号与Boost信号相同

•在处理Boost信号的插槽中,我发出新的Qt信号,将指针移过

•我现在创建了一个接收指针的新Qt插槽

Qt信号和插槽自动排列跨线程信号(注1)。 因此处理Boost信号的插槽仍在请求线程中。 然后它发出Qt信号。 Qt检测到信号的接收者在主线程中(注释2),但发送者在请求者线程中,并对信号进行排队。 当主线程中的主Qt事件循环将此排队事件从事件列表中拉出时,它会自动重新发出信号,但现在它位于主线程上下文中并允许UI操作。

注1 - 除非在connect()调用中显式覆盖此行为 - 请参阅Qt :: ConnectionType的文档。

注2 - 实际上,接收器的QObject由主线程拥有。 每个QObject都保留其创建的线程上下文的线程ID。

我希望这有助于解释线程发生的情况。 您的解决方案很好,但正如@tmpearce所建议的那样,在适配器中包装可能很方便。

将showObject定义为slot并将小公式添加到其正文中:

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

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

     return;
}

按原样保留方法体的其余部分。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM