How to properly execute GUI operations in Qt main thread?

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.


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;