在具有 GCD 的 ObjC 中,有一种执行 lambda 的方法在任何旋转事件循环的线程中。例如:
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
或:
dispatch_async(dispatch_get_main_queue(), ^{ /* do sth */ });
它在主线程中执行某些操作(相当于 C 中的 []{ / do sth / })队列,阻塞或异步。
我怎样才能做同样的事情Qt?
根据我所读到的内容,我猜解决方案是以某种方式向主线程的某个对象发送信号。但什么对象呢?只是 QApplication::instance() 吗? (这是此时主线程中唯一存在的对象。)什么信号?
这当然是可能的。任何解决方案都将集中于传递一个事件,将函子包装到驻留在所需线程中的消费者对象。我们将这个操作称为元调用发布。详细信息可以通过多种方式执行。
// invoke on the main thread QMetaObject::invokeMethod(qApp, []{ ... }); // invoke on an object's thread QMetaObject::invokeMethod(obj, []{ ... }); // invoke on a particular thread QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(thread), []{ ... });
// https://github.com/KubaO/stackoverflown/tree/master/questions/metacall-21646467 // Qt 5.10 & up - it's all done template <typename F> static void postToObject(F &&fun, QObject *obj = qApp) { QMetaObject::invokeMethod(obj, std::forward<F>(fun)); } template <typename F> static void postToThread(F && fun, QThread *thread = qApp->thread()) { auto *obj = QAbstractEventDispatcher::instance(thread); Q_ASSERT(obj); QMetaObject::invokeMethod(obj, std::forward<F>(fun)); } // Qt 5/4 - preferred, has least allocations namespace detail { template <typename F> struct FEvent : public QEvent { using Fun = typename std::decay<F>::type; Fun fun; FEvent(Fun && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} FEvent(const Fun & fun) : QEvent(QEvent::None), fun(fun) {} ~FEvent() { fun(); } }; } template <typename F> static void postToObject(F && fun, QObject * obj = qApp) { if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - consider using postToThread"; QCoreApplication::postEvent(obj, new detail::FEvent<F>(std::forward<F>(fun))); } template <typename F> static void postToThread(F && fun, QThread * thread = qApp->thread()) { QObject * obj = QAbstractEventDispatcher::instance(thread); Q_ASSERT(obj); QCoreApplication::postEvent(obj, new detail::FEvent<F>(std::forward<F>(fun))); }
void test1() { QThread t; QObject o; o.moveToThread(&t); // Execute in given object's thread postToObject([&]{ o.setObjectName("hello"); }, &o); // or postToObject(std::bind(&QObject::setObjectName, &o, "hello"), &o); // Execute in given thread postToThread([]{ qDebug() << "hello from worker thread"; }); // Execute in the main thread postToThread([]{ qDebug() << "hello from main thread"; }); }
// Qt 5/4 template <typename T, typename R> static void postToObject(T * obj, R(T::* method)()) { struct Event : public QEvent { T * obj; R(T::* method)(); Event(T * obj, R(T::*method)()): QEvent(QEvent::None), obj(obj), method(method) {} ~Event() { (obj->*method)(); } }; if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - this may be a bug"; QCoreApplication::postEvent(obj, new Event(obj, method)); } void test2() { QThread t; struct MyObject : QObject { void method() {} } obj; obj.moveToThread(&t); // Execute in obj's thread postToObject(&obj, &MyObject::method); }
以上所有方法都在没有事件循环的线程中工作。由于 QTBUG-66458,QTimer::singleShot 的方便使用也需要源线程中的事件循环。那么 postToObject 就变得非常简单,你可以直接使用 QTimer::singleShot,尽管它是一个尴尬的名字,对那些不熟悉这个习惯用法的人隐藏了意图。即使您不需要类型检查,通过命名的函数来更好地指示意图的间接也是有意义的:
template <typename F> static void postToObject(F && fun, QObject * obj = qApp) { if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - consider using postToThread"; QTimer::singleShot(0, obj, std::forward<F>(fun)); }
让我们根据以下通用代码。最简单的解决方案会将事件发布到应用程序对象(如果目标线程是主线程)或任何其他给定线程的事件调度程序。由于事件调度程序仅在输入 QThread::run 后才存在,因此我们通过从 needRunningThread 返回 true 来指示线程运行的要求。
#ifndef HAS_FUNCTORCALLCONSUMER namespace FunctorCallConsumer { bool needsRunningThread() { return true; } QObject * forThread(QThread * thread) { Q_ASSERT(thread); QObject * target = thread == qApp->thread() ? static_cast<QObject*>(qApp) : QAbstractEventDispatcher::instance(thread); Q_ASSERT_X(target, "postMetaCall", "the receiver thread must have an event loop"); return target; } } #endif
元调用发布函数,以最简单的形式,要求函子调用消费者为给定线程提供对象,并实例化函子调用事件。事件的实现还在前面,是各种实现之间的本质区别。
#ifndef HAS_POSTMETACALL void postMetaCall(QThread * thread, const std::function<void()> & fun) { auto receiver = FunctorCallConsumer::forThread(thread); QCoreApplication::postEvent(receiver, new FunctorCallEvent(fun, receiver)); } void postMetaCall(QThread * thread, std::function<void()> && fun) { auto receiver = FunctorCallConsumer::forThread(thread); QCoreApplication::postEvent(receiver, new FunctorCallEvent(std::move(fun), receiver)); } #endif
出于演示目的,工作线程首先向主线程发送元调用,然后推迟到 QThread ::run() 启动事件循环以侦听来自其他线程的可能元调用。如果使用者的实现需要的话,互斥体用于允许线程用户以简单的方式等待线程启动。对于上面给出的默认事件使用者来说,这样的等待是必要的。
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
以上是如何在 Qt 中的给定线程中执行函子或 Lambda,类似于 GCD?的详细内容。更多信息请关注PHP中文网其他相关文章!