如何将lambda函数排队到Qt的事件循环中?

Tom*_*ica 6 c++ lambda qt event-loop qthread

基本上我需要在Java中完成同样的事情:

SwingUtilities.invokeLater(()->{/* function */});
Run Code Online (Sandbox Code Playgroud)

或者在javascript中这样:

setTimeout(()=>{/* function */}, 0);
Run Code Online (Sandbox Code Playgroud)

但是用Qt和lambda.所以有些伪代码:

Qt::queuePushMagic([]() { /* function */ });
Run Code Online (Sandbox Code Playgroud)

作为一个额外的复杂性,我需要这个在多线程上下文中工作.我实际上要做的是在正确的线程中自动运行某些方法.那么代码看起来如何:

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

这该怎么做?

Rei*_*ica 11

你的问题是如何利用Qt使QObject方法线程安全?让我们根据您的用例调整那里提供的解决方案.首先,让我们考虑安全检查:

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();
}
Run Code Online (Sandbox Code Playgroud)

你建议的方法需要一个仿函数,并让编译器处理在仿函数中打包参数(如果有的话):

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)));
}
Run Code Online (Sandbox Code Playgroud)

第二种方法在事件中显式存储所有参数的副本,并且不使用仿函数:

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)...});
}
Run Code Online (Sandbox Code Playgroud)

它的用法如下:

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;
   }
};
Run Code Online (Sandbox Code Playgroud)

测试工具:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

postCall 
postCall 
postCall 
method1 
method2 
Run Code Online (Sandbox Code Playgroud)

以上编译和使用Qt 4或Qt 5.

另请参阅此问题,探索在Qt中调用其他线程上下文中的仿函数的各种方法.

  • 发布到给定对象的事件在切换线程时跟踪对象,因此这不是问题.但是`this-> moveToThread(this)`是一种反模式.你不需要触摸`QThread`.将您的代码放入`QObject`并将其移动到已经运行的线程. (2认同)
  • 一般来说,在线程中使用`QObject`时,你根本不需要继承`QThread`除了使其析构函数合理(例如`~MyThread(){quit(); wait();}`.本质上,做你正在做的事情,但继承自`QObject`,而不是`QThread`.你可以使用`safe`的变体强制延迟一个方法调用,直到后来从构造函数,如果你想推迟一些构造工作直到控件返回到事件循环.如果对象和线程之间有1:1的关系,你可以让对象保持其线程 - 见上文. (2认同)

Jea*_*ier 6

从 Qt 5.10 开始,您可以执行以下操作:

QMetaObject::invokeMethod(obj, [] { });
Run Code Online (Sandbox Code Playgroud)

哪里obj是一个 QObject,它被分配给你希望你的东西运行的线程。

  • 并确保它到达队列的末尾(即使当前执行已经与目标 `obj` 在同一线程上),您可以显式指定 `Qt::QueuedConnection` 参数:`QMetaObject::invokeMethod (obj, "doSomething", Qt::QueuedConnection);` (2认同)