13

Basically I need the same thing that is done like this in Java:

SwingUtilities.invokeLater(()->{/* function */});

Or like this in javascript:

setTimeout(()=>{/* function */}, 0);

But with Qt and lambda. So some pseudocode:

Qt::queuePushMagic([]() { /* function */ });

As an additional complication, I need this to work in multithreaded context. What I'm actually trying to do is to automatically run certain methods in correct thread. What the code would then look:

SomeClass::threadSafeAsyncMethod() {
    if(this->thread() != QThread::currentThread()) {
        Qt::queuePushMagic([this]()=>{ this->threadSafeAsyncMethod() });
        return;
    }
}

How to do this?

sandwood
  • 2,038
  • 20
  • 38
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778

3 Answers3

12

Your problem is of How to leverage Qt to make a QObject method thread-safe? Let's adapt the solutions offered there to your use case. First, let's factor out the safety check:

bool isSafe(QObject * obj) {
   Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
   auto thread = obj->thread() ? obj->thread() : qApp->thread();
   return thread == QThread::currentThread();
}

The approach you suggested takes a functor, and lets the compiler deal with packing up the arguments (if any) within the functor:

template <typename Fun> void postCall(QObject * obj, Fun && fun) {
   qDebug() << __FUNCTION__;
   struct Event : public QEvent {
      using F = typename std::decay<Fun>::type;
      F fun;
      Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
      Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
      ~Event() { fun(); }
   };
   QCoreApplication::postEvent(
            obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

A second approach stores the copies of all the parameters explicitly within the event and doesn't use a functor:

template <typename Class, typename... Args>
struct CallEvent : public QEvent {
   // See https://stackoverflow.com/a/7858971/1329652
   // See also https://stackoverflow.com/a/15338881/1329652
   template <int ...> struct seq {};
   template <int N, int... S> struct gens { using type = typename gens<N-1, N-1, S...>::type; };
   template <int ...S>        struct gens<0, S...> { using type = seq<S...>; };
   template <int ...S>        void callFunc(seq<S...>) { (obj->*method)(std::get<S>(args)...); }
   Class * obj;
   void (Class::*method)(Args...);
   std::tuple<typename std::decay<Args>::type...> args;
   CallEvent(Class * obj, void (Class::*method)(Args...), Args&&... args) :
      QEvent(QEvent::None), obj(obj), method(method), args(std::move<Args>(args)...) {}
   ~CallEvent() { callFunc(typename gens<sizeof...(Args)>::type()); }
};

template <typename Class, typename... Args> void postCall(Class * obj, void (Class::*method)(Args...), Args&& ...args) {
   qDebug() << __FUNCTION__;
   QCoreApplication::postEvent(
            obj->thread() ? static_cast<QObject*>(obj) : qApp, new CallEvent<Class, Args...>{obj, method, std::forward<Args>(args)...});
}

It's used as follows:

struct Class : QObject {
   int num{};
   QString str;
   void method1(int val) {
      if (!isSafe(this))
         return postCall(this, [=]{ method1(val); });
      qDebug() << __FUNCTION__;
      num = val;
   }
   void method2(const QString &val) {
      if (!isSafe(this))
         return postCall(this, &Class::method2, val);
      qDebug() << __FUNCTION__;
      str = val;
   }
};

A test harness:

// https://github.com/KubaO/stackoverflown/tree/master/questions/safe-method-40382820
#include <QtCore>

// above code

class Thread : public QThread {
public:
   Thread(QObject * parent = nullptr) : QThread(parent) {}
   ~Thread() { quit(); wait(); }
};

void moveToOwnThread(QObject * obj) {
  Q_ASSERT(obj->thread() == QThread::currentThread());
  auto thread = new Thread{obj};
  thread->start();
  obj->moveToThread(thread);
}

int main(int argc, char ** argv) {
   QCoreApplication app{argc, argv};
   Class c;
   moveToOwnThread(&c);

   const auto num = 44;
   const auto str = QString::fromLatin1("Foo");
   c.method1(num);
   c.method2(str);
   postCall(&c, [&]{ c.thread()->quit(); });
   c.thread()->wait();
   Q_ASSERT(c.num == num && c.str == str);
}

Output:

postCall 
postCall 
postCall 
method1 
method2 

The above compiles and works with either Qt 4 or Qt 5.

See also this question, exploring various ways of invoking functors in other thread contexts in Qt.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • This looks great. I was already playing with other proposals and stumbled over one more issue - what if the thread is not running *yet*? It might be problem in my design. What I do is that I call `this->moveToThread(this)` in reimplemented version of `QThread::run`. I'm not even sure if this issue is related, or if I'm just doing it wrong. – Tomáš Zato Mar 02 '17 at 22:13
  • 2
    The events posted to a given object track the object as it switches threads, so that's not a problem. But `this->moveToThread(this)` is an anti-pattern. You don't need to touch `QThread`. Put your code into a `QObject` and move that to a thread that already runs. – Kuba hasn't forgotten Monica Mar 02 '17 at 22:18
  • So I'm not supposed to maintain `QThread` subclasses that then run in the thread they represent? I thought it works like that. I could of course make the thread separatelly, but I thought that's just more code. – Tomáš Zato Mar 02 '17 at 22:37
  • 2
    Generally speaking, when using `QObject` in threads, you don't need to subclass `QThread` at all except to make its destructor sensible (e.g. `~MyThread() { quit(); wait(); }`. Essentially, do what you're doing, but inherit from `QObject`, not `QThread`. You can use a variant of `safe` to forcibly delay a method call until later from the constructor, if you'd want to defer some of the construction work until the control returns to the event loop. If you have a 1:1 relationship between objects and threads, you can have the object keep its thread - see above. – Kuba hasn't forgotten Monica Mar 02 '17 at 22:49
  • Excellent, but what if `Class::method1`/`Class::method2` were supposed to return some type of value, can we have some sort of `Qt::BlockingQueuedConnection` to have the method call itself safely (in the thread where the `QObject` lives) and return back that value? maybe in a way that is similar to [`QMetaObject::invokeMethod`](https://doc.qt.io/qt-5/qmetaobject.html#invokeMethod) but without using introspection? – Mike Mar 03 '17 at 01:57
  • 1
    There are two ways of doing it: one is via a blocking connection, another is via a notification signal. Generally speaking, blocking the GUI thread is a very bad idea, so notification is preferred. I'll modify the example to show how it could be done. – Kuba hasn't forgotten Monica Mar 03 '17 at 13:16
9

As of Qt 5.10 you can just do :

QMetaObject::invokeMethod(obj, [] { });

where obj is a QObject which is assigned to the thread you want your stuff to run in.

Jean-Michaël Celerier
  • 7,412
  • 3
  • 54
  • 75
  • 3
    and to make sure it goes to the end of queue (even in cases where current execution is already on the same thread as the targeted `obj`), you can explicitly specify the `Qt::QueuedConnection` argument: `QMetaObject::invokeMethod(obj, "doSomething", Qt::QueuedConnection);` – pestophagous Jan 16 '19 at 00:04
1

if you use

QMetaObject::invokeMethod(obj, [] { });

from the event loop thread, lambda get executed instantly, but

QTimer::singleShot(1, [=]() {});

runs after all other tasks. also you can write this to give the loop more time to process pending events at this time:

QTimer::singleShot(1, [=]() {
  QApplication::processEvents();  
  // .....
});
Ali
  • 21,572
  • 15
  • 83
  • 95