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;