简体   繁体   English

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

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

I have the following problem: Our main application uses the Qt toolkit for showing windows and user interaction. 我有以下问题:我们的主要应用程序使用Qt工具包显示窗口和用户交互。 A large part of our application, however, is ignorant of the GUI part. 然而,我们的大部分应用程序都不了解GUI部分。 I now created the following design: 我现在创建了以下设计:

  • There is a singleton class that may request rendering for a given object (OpenSceneGraph node; but this is irrelevant for the question) 有一个单例类可以请求呈现给定对象(OpenSceneGraph节点;但这与问题无关)
  • The rendering request causes the singleton to emit a signal 渲染请求使单例发出信号
  • There is a slot in the main window class (which uses Qt) to handle rendering the object 主窗口类(使用Qt)中有一个槽来处理渲染对象
  • Currently, the slot only creates a new text edit widget and places it in an QMdiArea of the main window 目前,插槽只创建一个新的文本编辑小部件并将其放在主窗口的QMdiArea

However, the application inevitably crashes when I try to create a new widget. 但是,当我尝试创建一个新的小部件时,应用程序不可避免地崩溃 The error messages area: 错误消息区域:

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

After perusing stackoverflow, I found similar questions (which were not readily applicable to this situation). 在仔细阅读stackoverflow之后,我发现了类似的问题(这些问题并不适用)。 Obviously, Qt doesn't like it when I change something in the main window from another thread. 显然,当我从另一个线程更改主窗口中的某些内容时,Qt不喜欢它。 However, I did not consciously create the new thread and I thought that the singleton (which is created in the main function right after a call to QApplication() ) should be in the same thread as Qt. 但是,我并没有有意识地创建新线程,我认为单例(在调用QApplication()之后在main函数中创建) 应该与Qt在同一个线程中。 Apparently, I am mistaken. 显然,我错了。

Here is a minimal example that shows the things I am doing (I have extracted the relevant parts of the code, so the example is not exactly functional): 这是一个显示我正在做的事情的最小例子(我已经提取了代码的相关部分,因此示例不完全正常):

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();
  }
};

My attempts to solve this problem have been very clumsy: 我试图解决这个问题非常笨拙:

  • I created a new Qt signal in the MainWindow class with the same signature as the Boost signal 我在MainWindow类中创建了一个新的Qt信号,其信号与Boost信号相同
  • In the slot that handles the Boost signal, I emit the new Qt signal, passing the pointer over 在处理Boost信号的插槽中,我发出新的Qt信号,将指针移过
  • I now created a new Qt slot that receives the pointer 我现在创建了一个接收指针的新Qt插槽

When I open a new window in the new slot, everything works. 当我在新插槽中打开一个新窗口时,一切正常。 However, this strikes me as very clumsy. 然而,这让我非常笨拙。 Do I have to cascade all Boost signals like that or is there a better way? 我是否必须像这样级联 所有 Boost信号,还是有更好的方法?

I think what is confusing is that the call to the singleton from the rendering request is being made from whatever thread is generating the request. 我认为令人困惑的是,渲染请求中对单例的调用是从生成请求的任何线程中产生的。 The singleton will return a unique object, but the signal it sends it still in the context of the requesting thread. 单例将返回一个唯一的对象,但它发送的信号仍然在请求线程的上下文中。 Something has to be done to explicitly cause or allow a thread context switch to the main UI thread in order to actually process this signal and create the UI objects in the main thread. 必须做一些事情才能显式地导致或允许线程上下文切换到主UI线程,以便实际处理此信号并在主线程中创建UI对象。

And you are doing this implictly in this sequence you describe: 你正在按照你描述的顺序暗示​​这样做:

•I created a new Qt signal in the MainWindow class with the same signature as the Boost signal •我在MainWindow类中创建了一个新的Qt信号,其信号与Boost信号相同

•In the slot that handles the Boost signal, I emit the new Qt signal, passing the pointer over •在处理Boost信号的插槽中,我发出新的Qt信号,将指针移过

•I now created a new Qt slot that receives the pointer •我现在创建了一个接收指针的新Qt插槽

Qt signals and slots automatically queue cross-thread signals (note 1). Qt信号和插槽自动排列跨线程信号(注1)。 So the slot that handles the Boost signal is still in the requesting thread. 因此处理Boost信号的插槽仍在请求线程中。 It then emits the Qt signal. 然后它发出Qt信号。 Qt detects that the receiver of the signal is in the main thread (note 2) but the sender is in the requester thread, and queues the signal. Qt检测到信号的接收者在主线程中(注释2),但发送者在请求者线程中,并对信号进行排队。 When the main Qt event loop in the main thread pulls this queued event off the event list, it then automatically re-emits the signal, but now it is in the main thread context and UI operations are allowed. 当主线程中的主Qt事件循环将此排队事件从事件列表中拉出时,它会自动重新发出信号,但现在它位于主线程上下文中并允许UI操作。

note 1 - unless this behavior is explicitly overridden in the connect() call - see the documentation for Qt::ConnectionType . 注1 - 除非在connect()调用中显式覆盖此行为 - 请参阅Qt :: ConnectionType的文档。

note 2 - really, that the QObject of the receiver is owned by the main thread. 注2 - 实际上,接收器的QObject由主线程拥有。 Every QObject retains the thread id of the thread context it was created in. 每个QObject都保留其创建的线程上下文的线程ID。

I hope this helps explain what is happening with the threads. 我希望这有助于解释线程发生的情况。 Your solution is fine, but as @tmpearce suggested it might be convenient to wrap things up in an adapter. 您的解决方案很好,但正如@tmpearce所建议的那样,在适配器中包装可能很方便。

Define showObject as slot and add little formula to its body: 将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;
}

keep rest of your method body as is. 按原样保留方法体的其余部分。

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

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