如何在 Qt、GCD 风格的给定线程中执行函子或 lambda? [英] How to execute a functor or a lambda in a given thread in Qt, GCD-style?

查看:23
本文介绍了如何在 Qt、GCD 风格的给定线程中执行函子或 lambda?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在带有 GCD 的 ObjC 中,有一种方法可以在任何旋转事件循环的线程中执行 lambda.例如:

In ObjC with GCD, there is a way of executing a lambda in any of the threads that spin an event loop. For example:

dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });

或:

dispatch_async(dispatch_get_main_queue(), ^{ /* do sth */ });

它在主线程的队列中执行一些(相当于C++中的[]{/* do sth */}),要么阻塞要么异步.

It executes something (equivalent to []{ /* do sth */ } in C++) in the main thread's queue, either blocking or asynchronously.

如何在 Qt 中做同样的事情?

How can I do the same in Qt?

从我读到的内容来看,我想解决方案是以某种方式向主线程的某个对象发送信号.但什么对象?只是QApplication::instance()?(那是当时主线程中唯一存在的对象.)什么信号?

From what I have read, I guess the solution would be somehow to send a signal to some object of the main thread. But what object? Just QApplication::instance()? (That is the only object living in the main thread at that point.) And what signal?

从目前的答案和我目前的研究来看,我似乎真的需要一些虚拟对象来坐在主线程中,并带有一些插槽,它只是等待输入一些代码来执行.

From the current answers and my current research, it really seems that I need some dummy object to sit in the main thread with some slot which just waits to get in some code to execute.

所以,我决定子类化 QApplication 来添加它.我当前的代码不起作用(但也许您可以提供帮助):

So, I decided to subclass QApplication to add that. My current code, which doesn't work (but maybe you can help):

#include <QApplication>
#include <QThread>
#include <QMetaMethod>
#include <functional>
#include <assert.h>

class App : public QApplication
{
    Q_OBJECT

public:
    App();

signals:

public slots:
    void genericExec(std::function<void(void)> func) {
        func();
    }

private:
    // cache this
    QMetaMethod genericExec_method;
public:
    void invokeGenericExec(std::function<void(void)> func, Qt::ConnectionType connType) {
        if(!genericExec_method) {
            QByteArray normalizedSignature = QMetaObject::normalizedSignature("genericExec(std::function<void(void)>)");
            int methodIndex = this->metaObject()->indexOfSlot(normalizedSignature);
            assert(methodIndex >= 0);
            genericExec_method = this->metaObject()->method(methodIndex);
        }
        genericExec_method.invoke(this, connType, Q_ARG(std::function<void(void)>, func));
    }

};

static inline
void execInMainThread_sync(std::function<void(void)> func) {
    if(qApp->thread() == QThread::currentThread())
        func();
    else {
        ((App*) qApp)->invokeGenericExec(func, Qt::BlockingQueuedConnection);
    }
}

static inline
void execInMainThread_async(std::function<void(void)> func) {
    ((App*) qApp)->invokeGenericExec(func, Qt::QueuedConnection);
}

推荐答案

这当然是可能的.任何解决方案都将集中在传递一个将函子包装到驻留在所需线程中的消费者对象的事件上.我们将此操作称为元调用发布.这些细节可以通过多种方式执行.

It is certainly possible. Any solution will center on delivering an event that wraps the functor to a consumer object residing in the desired thread. We shall call this operation metacall posting. The particulars can be executed in several ways.

// 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),
                         []{ ... });

TL;用于函子的 DR

// 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)));
}

// Qt 5 - alternative version

template <typename F>
static void postToObject2(F && fun, QObject * obj = qApp) {
   if (qobject_cast<QThread*>(obj))
      qWarning() << "posting a call to a thread object - consider using postToThread";
   QObject src;
   QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun),
                    Qt::QueuedConnection);
}

template <typename F>
static void postToThread2(F && fun, QThread * thread = qApp->thread()) {
   QObject * obj = QAbstractEventDispatcher::instance(thread);
   Q_ASSERT(obj);
   QObject src;
   QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun),
                    Qt::QueuedConnection);
}

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"; });
}

TL;DR 方法/槽

// 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);
}

TL;DR:单发定时器怎么样?

上述所有方法都适用于没有事件循环的线程.由于 QTBUG-66458QTimer::singleShot 的方便挪用code> 在源线程中也需要一个事件循环.然后 postToObject 变得非常简单,你可以直接使用 QTimer::singleShot ,尽管它是一个尴尬的名字,对那些不熟悉这个习语的人隐藏了意图.即使您不需要类型检查,通过命名为更好地指示意图的函数进行的间接也是有意义的:

TL;DR: What about a single shot timer?

All of the above methods work from threads that don't have an event loop. Due to QTBUG-66458, the handy appropriation of QTimer::singleShot needs an event loop in the source thread as well. Then postToObject becomes very simple, and you could possibly just use QTimer::singleShot directly, although it's an awkward name that hides the intent from those unfamiliar with this idiom. The indirection via a function named to better indicate the intent makes sense, even if you don't need the type check:

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 后才会存在,我们通过从 needsRunningThread 返回 true 来指示线程运行的要求.

Common Code

Let's define our problem in terms of the following common code. The simplest solutions will post the event to either the application object, iff the target thread is the main thread, or to an event dispatcher for any other given thread. Since the event dispatcher will exist only after QThread::run has been entered, we indicate the requirement for the thread to be running by returning true from needsRunningThread.

#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

元调用发布函数,以其最简单的形式,要求函子调用消费者为给定线程提供对象,并实例化函子调用事件.事件的实现还在前面,是各种实现的本质区别.

The metacall posting functions, in their simplest form, require the functor call consumer to provide object for a given thread, and instantiate the functor call event. The implementation of the event is still ahead of us, and is the essential difference between various implementations.

第二个重载接受函子的右值引用,潜在地节省了对函子的复制操作.如果延续包含复制成本高昂的数据,这会很有帮助.

The second overload takes a rvalue reference for the functor, potentially saving a copy operation on the functor. This is helpful if the continuation contains data that is expensive to copy.

#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() 启动事件循环以侦听来自其他线程的可能的元调用.如果消费者的实现需要,互斥用于允许线程用户以简单的方式等待线程启动.对于上面给出的默认事件使用者来说,这种等待是必要的.

For demonstration purposes, the worker thread first posts a metacall to the main thread, and then defers to QThread::run() to start an event loop to listen for possible metacalls from other threads. A mutex is used to allow the thread user to wait in a simple fashion for the thread to start, if necessitated by the consumer's implementation. Such wait is necessary for the default event consumer given above.

class Worker : public QThread {
   QMutex m_started;
   void run() {
      m_started.unlock();
      postMetaCall(qApp->thread(), []{
         qDebug() << "worker functor executes in thread" << QThread::currentThread();
      });
      QThread::run();
   }
public:
   Worker(QObject * parent = 0) : QThread(parent) { m_started.lock(); }
   ~Worker() { quit(); wait(); }
   void waitForStart() { m_started.lock(); m_started.unlock(); }
};

最后,我们启动上面的工作线程,向主(应用程序)线程发布元调用,应用程序线程向工作线程发布元调用.

Finally, we start the above worker thread that posts a metacall to the main (application) thread, and the application thread posts a metacall to the worker thread.

int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
   a.thread()->setObjectName("main");
   Worker worker;
   worker.setObjectName("worker");
   qDebug() << "worker thread:" << &worker;
   qDebug() << "main thread:" << QThread::currentThread();
   if (FunctorCallConsumer::needsRunningThread()) {
      worker.start();
      worker.waitForStart();
   }
   postMetaCall(&worker, []{ qDebug() << "main functor executes in thread" << QThread::currentThread(); });
   if (!FunctorCallConsumer::needsRunningThread()) worker.start();
   QMetaObject::invokeMethod(&a, "quit", Qt::QueuedConnection);
   return a.exec();
}

在所有实现中,输出将大致如下.函子跨线程:在主线程中创建的函子在工作线程中执行,反之亦然.

The output will look approximately as follows in all implementations. The functors cross the threads: the one created in the main thread is executed in the worker thread, and vice-versa.

worker thread: QThread(0x7fff5692fc20, name = "worker") 
main thread: QThread(0x7f86abc02f00, name = "main") 
main functor executes in thread QThread(0x7fff5692fc20, name = "worker") 
worker functor executes in thread QThread(0x7f86abc02f00, name = "main") 

Qt 5 使用临时对象作为信号源的解决方案

Qt 5 最简单的方法是使用临时 QObject 作为信号源,并将函子连接到它的 destroyed(QObject*) 信号.当 postMetaCall 返回时,signalSource 被销毁,发出它的 destroyed 信号,并将元调用发布到代理对象.

Qt 5 Solution Using a Temporary Object as The Signal Source

The simplest approach for Qt 5 is to use a temporary QObject as a signal source, and connect the functor to its destroyed(QObject*) signal. When postMetaCall returns, the signalSource gets destructed, emits its destroyed signal, and posts the metacall to the proxy object.

这可能是 C++11 风格中最简洁直接的实现.signalSource 对象在 C++11 RAII 方式中用于消除其销毁的副作用.短语副作用"在 C++11 的语义中具有含义,不应被解释为不可靠"或不受欢迎"——它绝非如此.QObject 与我们的约定是在其析构函数执行期间的某个时间发出 destroyed .我们非常欢迎使用这一事实.

This is perhaps the most concise and straightforward implementation in the C++11 style. The signalSource object is used in the C++11 RAII fashion for the side effects of its destruction. The phrase "side effects" has a meaning within C++11's semantics and should not be interpreted to mean "unreliable" or "undesirable" - it's anything but. QObject's contract with us is to emit destroyed sometime during the execution of its destructor. We're more than welcome to use that fact.

#include <QtCore>
#include <functional>

namespace FunctorCallConsumer { QObject * forThread(QThread*); }

#define HAS_POSTMETACALL
void postMetaCall(QThread * thread, const std::function<void()> & fun) {
   QObject signalSource;
   QObject::connect(&signalSource, &QObject::destroyed,
                    FunctorCallConsumer::forThread(thread), [=](QObject*){ fun(); });
}
#ifdef __cpp_init_captures
void postMetaCall(QThread * thread, std::function<void()> && fun) {
   QObject signalSource;
   QObject::connect(&signalSource, &QObject::destroyed,
                    FunctorCallConsumer::forThread(thread), [fun(std::move(fun))](QObject*){ fun(); });
}
#endif
// Common Code follows here

如果我们只打算发布到主线程,代码几乎变得微不足道:

If we only intend to post to the main thread, the code becomes almost trivial:

void postToMainThread(const std::function<void()> & fun) {
  QObject signalSource;
  QObject::connect(&signalSource, &QObject::destroyed, qApp, [=](QObject*){
    fun();
  });
}

#ifdef __cpp_init_captures
void postToMainThread(std::function<void()> && fun) {
  QObject signalSource;
  QObject::connect(&signalSource, &QObject::destroyed, qApp, [fun(std::move(fun))](QObject*){
    fun();
  });
}
#endif

使用 QEvent 析构函数的 Qt 4/5 解决方案

同样的方法可以直接应用于QEvent.事件的虚拟析构函数可以调用函子.事件在被消费者对象的线程的事件调度器传递后立即被删除,因此它们总是在正确的线程中执行.这在 Qt 4/5 中不会改变.

Qt 4/5 Solution Using QEvent Destructor

The same approach can be applied to QEvent directly. The event's virtual destructor can call the functor. The events are deleted right after they are delivered by the consumer object's thread's event dispatcher, so they always execute in the right thread. This will not change in Qt 4/5.

#include <QtCore>
#include <functional>

class FunctorCallEvent : public QEvent {
   std::function<void()> m_fun;
   QThread * m_thread;
public:
   FunctorCallEvent(const std::function<void()> & fun, QObject * receiver) :
      QEvent(QEvent::None), m_fun(fun), m_thread(receiver->thread()) {}
   FunctorCallEvent(std::function<void()> && fun, QObject * receiver) :
      QEvent(QEvent::None), m_fun(std::move(fun)), m_thread(receiver->thread()) { qDebug() << "move semantics"; }
   ~FunctorCallEvent() {
      if (QThread::currentThread() == m_thread)
         m_fun();
      else
         qWarning() << "Dropping a functor call destined for thread" << m_thread;
   }
};
// Common Code follows here

只发布到主线程,事情变得更简单:

To post to main thread only, things become even simpler:

class FunctorCallEvent : public QEvent {
   std::function<void()> m_fun;
public:
   FunctorCallEvent(const std::function<void()> & fun) :
      QEvent(QEvent::None), m_fun(fun) {}
   FunctorCallEvent(std::function<void()> && fun, QObject * receiver) :
      QEvent(QEvent::None), m_fun(std::move(fun)) {}
   ~FunctorCallEvent() {
      m_fun();
   }
};

void postToMainThread(const std::function<void()> & fun) {
   QCoreApplication::postEvent(qApp, new FunctorCallEvent(fun);
}

void postToMainThread(std::function<void()> && fun) {
   QCoreApplication::postEvent(qApp, new FunctorCallEvent(std::move(fun)));
}

使用私有 QMetaCallEvent 的 Qt 5 解决方案

函子可以包装在 QMetaCallEvent 的 Qt 5 slot 对象负载中.函子将被 QObject::event 调用,因此可以发布到目标线程中的任何对象.此解决方案使用 Qt 5 的私有实现细节.

Qt 5 Solution Using the Private QMetaCallEvent

The functor can be wrapped in the Qt 5 slot object payload of the QMetaCallEvent. The functor will be invoked by QObject::event, and thus can be posted to any object in the target thread. This solution uses the private implementation details of Qt 5.

#include <QtCore>
#include <private/qobject_p.h>
#include <functional>

class FunctorCallEvent : public QMetaCallEvent {
public:
   template <typename Functor>
   FunctorCallEvent(Functor && fun, QObject * receiver) :
      QMetaCallEvent(new QtPrivate::QFunctorSlotObject<Functor, 0, typename QtPrivate::List_Left<void, 0>::Value, void>
                     (std::forward<Functor>(fun)), receiver, 0, 0, 0, (void**)malloc(sizeof(void*))) {}
   // Metacalls with slot objects require an argument array for the return type, even if it's void.
};
// Common Code follows here

使用自定义事件和消费者的 Qt 4/5 解决方案

我们重新实现对象的 event() 方法,并让它调用函子.这在函子被发布到的每个线程中调用一个显式的事件消费者对象.对象在其线程完成时被清除,或者对于主线程,当应用程序实例被破坏时.它适用于 Qt 4 和 Qt 5.右值引用的使用避免了临时函子的复制.

Qt 4/5 Solution Using a Custom Event and Consumer

We reimplement the event() method of the object, and have it call the functor. This calls for an explicit event consumer object in each thread that the functors are posted to. The object is cleaned up when its thread is finished, or, for the main thread, when the application instance is destructed. It works on both Qt 4 and Qt 5. The use of rvalue references avoids copying of the temporary functor.

#include <QtCore>
#include <functional>

class FunctorCallEvent : public QEvent {
   std::function<void()> m_fun;
public:
   FunctorCallEvent(const std::function<void()> & fun, QObject *) :
      QEvent(QEvent::None), m_fun(fun) {}
   FunctorCallEvent(std::function<void()> && fun, QObject *) :
      QEvent(QEvent::None), m_fun(std::move(fun)) { qDebug() << "move semantics"; }
   void call() { m_fun(); }
};

#define HAS_FUNCTORCALLCONSUMER
class FunctorCallConsumer : public QObject {
   typedef QMap<QThread*, FunctorCallConsumer*> Map;
   static QObject * m_appThreadObject;
   static QMutex m_threadObjectMutex;
   static Map m_threadObjects;
   bool event(QEvent * ev) {
      if (!dynamic_cast<FunctorCallEvent*>(ev)) return QObject::event(ev);
      static_cast<FunctorCallEvent*>(ev)->call();
      return true;
   }
   FunctorCallConsumer() {}
   ~FunctorCallConsumer() {
      qDebug() << "consumer done for thread" << thread();
      Q_ASSERT(thread());
      QMutexLocker lock(&m_threadObjectMutex);
      m_threadObjects.remove(thread());
   }
   static void deleteAppThreadObject() {
      delete m_appThreadObject;
      m_appThreadObject = nullptr;
   }
public:
   static bool needsRunningThread() { return false; }
   static FunctorCallConsumer * forThread(QThread * thread) {
      QMutexLocker lock(&m_threadObjectMutex);
      Map map = m_threadObjects;
      lock.unlock();
      Map::const_iterator it = map.find(thread);
      if (it != map.end()) return *it;
      FunctorCallConsumer * consumer = new FunctorCallConsumer;
      consumer->moveToThread(thread);
      if (thread != qApp->thread())
         QObject::connect(thread, SIGNAL(finished()), consumer, SLOT(deleteLater()));
      lock.relock();
      it = m_threadObjects.find(thread);
      if (it == m_threadObjects.end()) {
         if (thread == qApp->thread()) {
            Q_ASSERT(! m_appThreadObject);
            m_appThreadObject = consumer;
            qAddPostRoutine(&deleteAppThreadObject);
         }
         m_threadObjects.insert(thread, consumer);
         return consumer;
      } else {
         delete consumer;
         return *it;
      }
   }
};

QObject * FunctorCallConsumer::m_appThreadObject = nullptr;
QMutex FunctorCallConsumer::m_threadObjectMutex;
FunctorCallConsumer::Map FunctorCallConsumer::m_threadObjects;
// Common Code follows here

这篇关于如何在 Qt、GCD 风格的给定线程中执行函子或 lambda?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆