3

I managed to implement a GUI Qt-based application in a worker C++ std::thread like described here. Now I need both the main and worker threads to communicate.

My question is: how can I communicate messages (an array of floats) from my main thread to the worker thread so that I can update the GUI?

I have an application that performs real-time signal processing. My goal is to create a Qt GUI that can be plugged in into my application to visualize various signals without influencing the real-time aspect. I investigated different methods of how to accomplish this and concluded that this post describes quite closely what I need and provides a solution for it. However, there is no information on how the main and worker threads can communicate with each other.

I tried using the Futures/Promises approach described here to accomplish the inter-thread communication. Although I was able to get this example running, I couldn't integrate it into my project. The reason is that this approach relies on having a busy loop inside the worker thread that is constantly checking whether a new message has been sent by the main thread. However, in a Qt application, the program blocks once it enters the main event loop in a.exec(). This prevents the busy loop check and hence causes the program to deadlock.

This is how I am spawning the GUI worker thread (based on this post).

#include <thread>

// Start the Qt realtime plot demo in a worker thread
int argc = 0;
char **argv = NULL;
std::thread t1
        (
                [&] {
                    QApplication application(argc, argv);
                    MainWindow mainWindow;
                    mainWindow.show();
                    return application.exec();
                }
        );
mroavi
  • 119
  • 2
  • 9
  • How exactly does the communication need to work? Shared memory with appropriate synchronisation should be sufficient. I can't see anything relevant to what you're decribing in your code. – Fureeish Sep 22 '19 at 13:23
  • I don't have a requirement of how the comm needs to work. I haven't looked into shared memory comm but if you think that this could help me accomplish what I need, I will look into it. So let's suppose that I start the GUI worker thread (using the code I posted). The thread will enter `application.exec()` and never return. How can I use the shared memory approach to send a message from my main to the worker thread such that it updates the GUI with the message? Do I need the signal/slot mechanisms to inform the GUI worker thread that a new message is available? – mroavi Sep 22 '19 at 13:59
  • 1
    You should be able to notify the UI thread by calling `QMetaObject::invokeMethod` with `Qt::QueuedConnection` connection type. That basically posts an event to Qt's event queue. – Igor Tandetnik Sep 22 '19 at 14:00
  • @IgorTandetnik How can I call `QMetaObject::invokeMethod` with `mainWindow` as an argument if `mainWindow` is defined inside the UI thread? – mroavi Sep 22 '19 at 14:41
  • Thanks Igor. That is definitely one way to go about it. Take a look at @ypnos answer. It provides some insight into why this might or might not be the best solution. – mroavi Sep 22 '19 at 17:06

2 Answers2

1

You can use Qt's own methods to communicate between the threads. Each Qt object has a thread affinity. If you create your MainWindow object in the separate GUI thread, it will be attached to that thread. If you use Qt signals and slots, and connect them with Qt::QueuedConnection, the slot will be called in the object's thread, through that thread's main loop, regardless of where the signal came from.

Note that you can define signals in the MainWindow class but invoke them from outside, as they are public. This means you do not need a separate sending object. In MainWindow constructor (for example) you can connect its own signal to a slot or lambda method.

To be able to emit the MainWindow signal, you can have a MainWindow pointer set to nullptr outside the GUI thread, and set that pointer (through lambda capture) to the new object you create inside the GUI thread. Outside your thread, whenever you want to emit a signal, you can do sth. like if (mainWindow) mainWindow->signal(…). I assume you need a check anyways as your GUI is optional.

Two notes:

  1. You could also forego the signal with QMetaObject::invokeMethod, it removes boilerplate, however signals can be connected through referencing the class method which is checked at compile time; The QMetaObject method uses string matching at runtime. Also with public signals, but protected slots, you can ensure that outside code doesn't accidentally call methods directly.
  2. When you connect the signal to a lambda, ensure that you specify the receiving object. Whereas connect(this, &MainWindow::signal, this, [this] {…}); will execute the lambda in the receiving thread, connect(this, &MainWindow::signal, [this] {…}); will not.
ypnos
  • 50,202
  • 14
  • 95
  • 141
  • Thank you so much for taking the time to write such a complete and precise answer. Even though I'm a complete Qt newbie I managed to understand and implement what you proposed in no time. – mroavi Sep 22 '19 at 16:52
  • Thank you for your feedback. Happy to hear that! – ypnos Sep 22 '19 at 17:04
  • Note that you're NOT allowed to touch any GUI classes at all in non-main threads. – peppe Sep 22 '19 at 17:58
  • @peppe To express it in slightly more technical terms, the vast majority of Qt is not thread-safe (signal emission being one of the few exceptions). Also, the underlying graphics system is often not thread-safe (that includes, e.g., X11). Which is the reason why Qt expects you to do all GUI operations in a single thread. – ypnos Sep 23 '19 at 06:23
  • 1
    "Vast majority of Qt" would be an exaggeration. GUI classes are not thread safe. Most of the rest (e.g. core classes, networking, ...) is reentrant. See also my slides here https://www.kdab.com/wp-content/uploads/stories/multithreading-with-qt-1.pdf . – peppe Sep 24 '19 at 07:08
  • Anyhow, above it says "If you create your MainWindow object in the separate thread", which worded like that is a recipe for problems! – peppe Sep 24 '19 at 07:09
  • @peppe, well, context matters. In the question it is explained in great length that the whole GUI oparates in that "separate thread". But thank you for the note about non-GUI reentrant stuff and the link to the slides! You explained the difference between thread-safe and reentrant well there, so I wonder why you counter my statement about thread-safety with reentrancy. – ypnos Sep 24 '19 at 15:34
  • I understand it better now, you are referring (context matters ;)) to your statement which was about reentrency and then I had talked about thread-safety. My comment was incomplete in this regard. – ypnos Sep 24 '19 at 15:42
0

For signal processing you generally do not want the overhead of qts virtual calls. But that aside,

A good solution for thread to gui communication is found in: git@github.com:midjji/convenient_multithreaded_qt_gui.git

then its just e.g.

enter code here
run_in_gui_thread(new RunEventImpl([](){
        QMainWindow* window=new QMainWindow();
        window->show();
    }));

callable from any thread, at any time, while taking care of setting things up for you in the bg.

midjji
  • 394
  • 3
  • 9