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

查看:78
本文介绍了如何在Gt风格的Qt的给定线程中执行函子或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);
}

推荐答案

当然可以.任何解决方案都将集中于传递一个事件,该事件将函子包装到驻留在所需线程中的使用者对象.我们将这个操作称为metacall发布.细节可以通过几种方式执行.

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-66458 ,对QTimer::singleShot的方便占用需要在事件循环中源线程.然后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

最简单形式的metacall发布函数要求functor调用使用者为给定线程提供对象,并实例化functor调用事件.该事件的实现仍然遥遥领先,这是各种实现之间的本质区别.

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

出于演示目的,工作线程首先将metacall发布到主线程,然后根据QThread::run()进行启动以启动事件循环以侦听来自其他线程的可能的metacall.互斥锁用于允许线程用户以简单的方式等待线程启动(如果消费者的实现需要).对于上面给出的默认事件使用者,这种等待是必需的.

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

最后,我们启动上面的工作线程,该工作线程将metacall张贴到主(应用程序)线程,而应用程序线程将metacall张贴到worker线程.

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信号,并将metacall发送到代理对象.

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插槽对象有效载荷中.该函子将由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和Qt5.使用右值引用可避免复制临时函子.

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

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

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