在具有 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中文網其他相關文章!