14

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.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 3
    Have a look at [Signals and Slots](http://doc.qt.io/qt-5/signalsandslots.html) – Mohamad Elghawi Dec 07 '15 at 14:17
  • @MohamadElghawi I know about signals and slots. But how exactly am I supposed to do that? I don't want to include Q_OBJECT in my TCP client class, so I can't just connect it to application. – Tomáš Zato Dec 07 '15 at 14:29

3 Answers3

9

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.

CJCombrink
  • 3,738
  • 1
  • 22
  • 38
  • +1. I would just say that the target method does not have to be a slot per se. As long as it is made available through the metatype system which can also be achieved by marking methods Q_INVOKABLE. – Mohamad Elghawi Dec 07 '15 at 15:10
  • Thanks, this looks great. Could you also please add info about how to define the method in `"mySlotName"`? Is that a normal `Q_SLOT`? – Tomáš Zato Dec 07 '15 at 15:16
  • Yes, it is a normal slot, I will add something shortly – CJCombrink Dec 08 '15 at 05:20
  • @TomášZato I have added some more code to the post to make it clear. As MohamadElghawi correctly pointed out, if the method is marked with [Q_INVOKABLE](http://doc.qt.io/qt-5/qobject.html#Q_INVOKABLE) it does not need to be a slot as the example code shows above. – CJCombrink Dec 08 '15 at 05:28
  • @TheBadger Thank you very much, this helped me a lot. – Tomáš Zato Dec 08 '15 at 13:56
  • Just be careful if you rename or change the function since the call uses a string to specify the function it will still compile but the call will fail, thus put some code in place to detect and identify if this happens – CJCombrink Dec 10 '15 at 06:23
  • Could use a bit more detail in your answer. – mike510a May 19 '16 at 07:56
  • @MikeNickaloff Perhaps, but what do you suggest? Should I discuss what invokeMethod does, or is the link to the Qt pages good enough to describe the function? Or should I give a better example to update a QLabel? The answer should give someone enough information to get them started, given the question and constraints. – CJCombrink May 19 '16 at 11:13
8

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;
Adriel Jr
  • 2,451
  • 19
  • 25
2

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

QMetaObject::invokeMethod(
          myQObject, [=]() { /* ... whatever ... */ }, Qt::QueuedConnection);
Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46