Handling a boost signal in Qt application with multiple threads

2.3k views Asked by At

I have the following problem: Our main application uses the Qt toolkit for showing windows and user interaction. A large part of our application, however, is ignorant of the GUI part. 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)
  • 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
  • Currently, the slot only creates a new text edit widget and places it in an QMdiArea of the main window

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). Obviously, Qt doesn't like it when I change something in the main window from another thread. 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. 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
  • In the slot that handles the Boost signal, I emit the new Qt signal, passing the pointer over
  • I now created a new Qt slot that receives the pointer

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?

2

There are 2 answers

0
PhysicalEd On BEST ANSWER

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.

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

•In the slot that handles the Boost signal, I emit the new Qt signal, passing the pointer over

•I now created a new Qt slot that receives the pointer

Qt signals and slots automatically queue cross-thread signals (note 1). So the slot that handles the Boost signal is still in the requesting thread. It then emits the Qt signal. 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. 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.

note 1 - unless this behavior is explicitly overridden in the connect() call - see the documentation for Qt::ConnectionType.

note 2 - really, that the QObject of the receiver is owned by the main thread. Every QObject retains the thread id of the thread context it was created in.

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.

0
Kamil Klimek On

Define showObject as slot and add little formula to its body:

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.