Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly execute GUI operations in Qt main thread?

I have a simple program that consists of two threads:

  1. Main GUI thread operated by Qt QApplication::exec
  2. TCP network thread operated by boost::asio::io_service

TCP events, such as connecting or receiving data cause changes in GUI. Most often, those are setText on QLabel and hiding various widgets. Currently, I am executing those actions in TCP client thread, which seems quite unsafe.

How to post properly an event to Qt Main thread? I am looking for Qt variant of boost::asio::io_service::strand::post, which posts event to boost::asio::io_service event loop.

like image 280
Tomáš Zato - Reinstate Monica Avatar asked Dec 07 '15 14:12

Tomáš Zato - Reinstate Monica


People also ask

Is QObject thread-safe?

QObject and all of its subclasses are not thread-safe. This includes the entire event delivery system. It is important to keep in mind that the event loop may be delivering events to your QObject subclass while you are accessing the object from another thread.

What is main thread in Qt?

As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads.

What is a GUI thread?

Graphical user interfaces often have a dedicated thread (“GUI thread”) for servicing user interactions. The thread must remain responsive to user requests even while the application has long computations running. For example, the user might want to press a “cancel” button to stop the long running computation.


2 Answers

If you do not want to make your TCP class a QObject another option is to use the QMetaObject::invokeMethod() function.

The requirement then is that your destination class must be a QObject and you must call a slot defined on the destination.

Say your QObject is defined as follow:

class MyQObject : public QObject {
    Q_OBJECT
public: 
    MyObject() : QObject(nullptr) {}
public slots:
    void mySlotName(const QString& message) { ... }
};

Then you can call that slot from your TCP Class.

#include <QMetaObject>

void TCPClass::onSomeEvent() {
    MyQObject *myQObject = m_object;
    myMessage = QString("TCP event received.");
    QMetaObject::invokeMethod(myQObject
                               , "mySlotName"
                               , Qt::AutoConnection // Can also use any other except DirectConnection
                               , Q_ARG(QString, myMessage)); // And some more args if needed
}

If you use Qt::DirectConnection for the invocation the slot will be executed in the TCP thread and it can/will crash.

Edit: Since invokeMethod function is static, you can call it from any class and that class does not need to be a QObject.

like image 146
CJCombrink Avatar answered Sep 22 '22 14:09

CJCombrink


If your object inherits from QObject, just emit a signal and connect (using the flag Qt::QueuedConnection) it to a slot in the main thread. Signals and Slots are thread safe and should be used preferably.

If it is not a QObject, than you may create a lambda function (with the GUI code) and use a single shot QTimer to queue it in the main thread and execute it in a callback. This is the code that I am using:

#include <functional>

void dispatchToMainThread(std::function<void()> callback)
{
    // any thread
    QTimer* timer = new QTimer();
    timer->moveToThread(qApp->thread());
    timer->setSingleShot(true);
    QObject::connect(timer, &QTimer::timeout, [=]()
    {
        // main thread
        callback();
        timer->deleteLater();
    });
    QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
}

...
// in a thread...

dispatchToMainThread( [&, pos, rot]{
    setPos(pos);
    setRotation(rot);
});

Original credit to https://riptutorial.com/qt/example/21783/using-qtimer-to-run-code-on-main-thread

Just be careful because if you delete your object your app may crash. Two options are:

  • call qApp->processEvents(); before removing to flush the queue;
  • queue the deletion also using dispatchToMainThread;
like image 39
Adriel Jr Avatar answered Sep 21 '22 14:09

Adriel Jr