基本上我需要在
Java中完成同样的事情:
SwingUtilities.invokeLater(()->{/* function */});
或者在javascript中这样:
setTimeout(()=>{/* function */},0);
但是用Qt和lambda.所以有些伪代码:
Qt::queuePushMagic([]() { /* function */ });
作为一个额外的复杂性,我需要这个在多线程上下文中工作.我实际上要做的是在正确的线程中自动运行某些方法.那么代码看起来如何:
SomeClass::threadSafeAsyncMethod() { if(this->thread() != QThread::currentThread()) { Qt::queuePushMagic([this]()=>{ this->threadSafeAsyncMethod() }); return; } }
这该怎么做?
解决方法
您的问题是
How to leverage Qt to make a QObject method thread-safe?让我们根据您的使用情况调整那里提供的解决方案.首先,让我们考虑安全检查:
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(); }
你建议的方法需要一个仿函数,并让编译器处理在仿函数中打包参数(如果有的话):
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))); }
第二种方法在事件中显式存储所有参数的副本,并且不使用仿函数:
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,Args&& ...args) { qDebug() << __FUNCTION__; QCoreApplication::postEvent( obj->thread() ? static_cast<QObject*>(obj) : qApp,new CallEvent<Class,Args...>{obj,method,std::forward<Args>(args)...}); }
它的用法如下:
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; } };
测试工具:
// 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); }
输出:
postCall postCall postCall method1 method2
以上编译和使用Qt 4或Qt 5.
另请参阅this question,探索在Qt中调用其他线程上下文中的仿函数的各种方法.