3

I want to invoke a slot of MyWidget

class MyWidget : public QWidget {
Q_OBJECT

public slots:
void onFooBar(const std::string&);/*std::string& could also be replaced
    by a QString for easier meta system handling*/
};

But because in my case boost::asio use, with threads I don't want to have to do anything with Qt, I want to invoke this slot from a thread different from the main thread but a random thread I don't control. (On of the threads I let run boost::asio of course)

How can I do this? QCoreApplication::postEvent seems to be a nice choice, but the docs don't point out a nice way, on how to create the necessary QEvent. QMetaObject::invokeMethod with Qt::QueuedConnection seems nice too, but isn't documented as thread safe.

So how can I safely invoke a qt slot from a non qt managed thread?

(Although the title of Boost asio with Qt suggests this could be a duplicate, the question seems completely different to me, this questions is not necessarily connected to boost::asio)

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • I just remembered that I once did something similar (somehow): [SO: Qt C++ Displaying images outside the GUI thread (Boost thread)](https://stackoverflow.com/a/47470395/7478597). May be, today I would use posted events instead of a `QTimer`... – Scheff's Cat Dec 16 '18 at 17:11
  • @Scheff The solution with `QMetaObject::invokeMethod` seems to be the common pattern, however in contrast to `QCoreApplication::postEvent` there is not thread safety documented. For this I've created a bug report, after this is resolved I will self answer this with that solution. https://bugreports.qt.io/browse/QTBUG-72599 – Superlokkus Dec 16 '18 at 17:52
  • 2
    `QMetaObject::invokeMethod` with a queued connection is implemented in terms of posting an event (see e.g. [here](https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qmetaobject.cpp.html#_ZNK11QMetaMethod6invokeEP7QObjectN2Qt14ConnectionTypeE22QGenericReturnArgument16QGenericArgumentS5_S5_S5_S5_S5_S5_S5_S5_S5_)). If you look at the code, it's as thread safe as `postEvent`. I'm not entirely sure why it's not documented as such; probably to avoid confusion around what the thread safety refers to, esp. if direct connections are involved (the "target" object is NOT covered by the safety promise!) – peppe Dec 16 '18 at 19:09
  • 1
    https://codereview.qt-project.org/#/c/248378/ adds the relevant bits of docs. – peppe Dec 16 '18 at 20:47

1 Answers1

3

Turns out QMetaObject::invokeMethod with Qt::QueuedConnection actually uses QCoreApplication::postEvent in its implementation (Thanks @peppe!). However the guarantee that it is thread safe, when

  1. used with Qt::QueuedConnection
  2. Lifetime of recipient managed by Qt (or AFAIK at least until after completed invocation)
  3. No other actions on recipient other than like this, from non main qt thread
  4. Lifetime of parameters managed by Qt (should be fine when using Q_ARS or call by value)

is not documented, yet. But I have created a bug report and qt forum discussion, and it seems it was intended to be so, and a documentation change ticket is already been created.

What I used in the end is the common pattern

class MyWidget : public QWidget {
Q_OBJECT

public slots:
void onFooBar(QString);
};

void asio_handler(const std::string& string, MyWidget* my_widget) {
QMetaObject::invokeMethod(
                        my_widget, "onFooBar", Qt::QueuedConnection,
                        Q_ARG(QString, QString::fromStdString(string))
                        );
}
Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • 1
    A few remarks. 1) `invokeMethod` is always thread-safe. However, the safety is about the logic inside `invokeMethod` itself, not about the target object. For instance, direct invocations on the same object from different threads will require the target object itself to be thread-safe. – peppe Dec 16 '18 at 23:03
  • 1
    2) Lifetime of target can be managed as you want, not necessarily using Qt facilities. The important part is that the target is alive during the call to `invokeMethod`. (In theory, it can be deleted immediately afterwards. With a queued connection, this may imply not invoking the target method.) – peppe Dec 16 '18 at 23:05
  • 1
    3) Is not true -- `invokeMethod` itself is thread-safe! For instance, you can safely schedule queued calls, to a given function, on the _same target object_, from multiple threads at the same time, without any manual synchronization. (The calls will then be placed from the target object's affinity thread.) You can even place multiple _direct_ calls on the same target object from multiple threads at the same time without synchronization -- but again, this requires the target object to then be thread safe. – peppe Dec 16 '18 at 23:08
  • 1
    4) When you place a queued call, the function parameters are _copied_ in the event posted behind the scenes, so you can safely delete them when `invokeMethod` returns. (That's why you need a call to `qRegisterMetaType` for the types the arguments, which in turn requires those types to be copiable. [Here's the loop](https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qmetaobject.cpp.html#2308) that copies the function arguments in the event object.) – peppe Dec 16 '18 at 23:12