Tomáš Zato
Tomáš Zato

Reputation: 53318

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.

Upvotes: 16

Views: 10382

Answers (3)

Dávid Tóth
Dávid Tóth

Reputation: 3255

Extending on the excellent answer of @CJCombrink; No need to define slots, you can also use lambda:

QMetaObject::invokeMethod(
          myQObject, [=]() { /* ... whatever ... */ }, Qt::QueuedConnection);

Upvotes: 7

Adriel Jr
Adriel Jr

Reputation: 2681

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;

Upvotes: 9

CJCombrink
CJCombrink

Reputation: 3950

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.

Upvotes: 11

Related Questions