I have a simple program that consists of two threads:
QApplication::exec
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.
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.
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.
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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With