如何将 lambda 函数排队到 Qt 的事件循环中?
How to queue lambda function into Qt's event loop?
基本上我需要在 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
// See also
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)...});
}
用法如下:
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/Whosebugn/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 中的其他线程上下文中调用仿函数的各种方法。
从 Qt 5.10 开始,您可以这样做:
QMetaObject::invokeMethod(obj, [] { });
其中 obj
是一个 QObject,它被分配给您希望您的东西进入的线程 运行。
如果你使用
QMetaObject::invokeMethod(obj, [] { });
从事件循环线程,lambda 立即执行,但是
QTimer::singleShot(1, [=]() {});
在所有其他任务之后运行。你也可以这样写,让循环有更多的时间来处理此时未决的事件:
QTimer::singleShot(1, [=]() {
QApplication::processEvents();
// .....
});
基本上我需要在 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
// See also
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)...});
}
用法如下:
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/Whosebugn/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 中的其他线程上下文中调用仿函数的各种方法。
从 Qt 5.10 开始,您可以这样做:
QMetaObject::invokeMethod(obj, [] { });
其中 obj
是一个 QObject,它被分配给您希望您的东西进入的线程 运行。
如果你使用
QMetaObject::invokeMethod(obj, [] { });
从事件循环线程,lambda 立即执行,但是
QTimer::singleShot(1, [=]() {});
在所有其他任务之后运行。你也可以这样写,让循环有更多的时间来处理此时未决的事件:
QTimer::singleShot(1, [=]() {
QApplication::processEvents();
// .....
});