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

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

问题描述

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

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

或:

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

它执行某些操作(相当于 [] {/ * do sth *



如何在Qt中执行同样的操作?



从我已经阅读的,我想解决方案将以某种方式发送信号到主线程的某些对象。但是什么对象?只需 QApplication :: instance()






从当前的答案和我目前的研究,真的似乎我需要一些虚拟对象坐在主线程与一些槽,只是等待一些代码执行。



,我决定子类 QApplication 来添加。我目前的代码,不工作(但也许你可以帮助):

  #include&Q; 
#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:
//缓存此
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){
( qApp) - > invokeGenericExec(func,Qt :: QueuedConnection);
}


解决方案

任何解决方案都将集中在传递一个事件,将事件包装到驻留在期望线程中的消费者对象。我们将这个操作称为metacall posting。



TL;为作家的DR



  // Qt 5/4  -  preferred,has least allocations 
template< typename F>
static void postToThread(F&& fun,QObject * obj = qApp){
struct Event:public QEvent {
F fun;
Event(F& amp; fun):QEvent(QEvent :: None),fun(std :: move(fun)){}
〜Event(){fun }
};
QCoreApplication :: postEvent(obj,new Event(std :: move(fun)));
}

// Qt 5 - 备选版本
模板< typename F>
static void postToThread(F&& fun,QObject * obj = qApp){
QObject src;
QObject :: connect(& src,& QObject :: destroyed,obj,std :: move(fun),Qt :: QueuedConnection);
}

QThread t;
QObject o;
o.moveToThread(& t);

//在给定对象的线程中执行
postToThread([&] {o.setObjectName(hello);},& o);
//或
postToThread(std :: bind(& QObject :: setObjectName,& o,hello),& o);

//在主线程中执行
postToThread([] {qDebug()<<hello;});



TL;方法/插槽的DR



  // Qt 5/4 
template< typename T,typename R>
static void postToThread(T * obj,R(T :: * method)()){
struct Event:public QEvent {
T * obj;
R(T :: * method)();
事件(T * obj,R(T :: * method)()):
QEvent(QEvent :: None),obj(obj) Event(){(obj-> * method)(); }
};
QCoreApplication :: postEvent(obj,new Event(obj,method));
}

QThread t;
struct MyObject:QObject {void method(){}} obj;
obj.moveToThread(& t);

//在obj的线程中执行
postToThread(& obj,& MyObject :: method);



通用代码



根据以下常用代码。最简单的解决方案将事件发布到应用程序对象,如果目标线程是主线程,或任何其他给定线程的事件分派器。由于只有在输入 QThread :: run 之后事件分派器才会存在,我们通过从 needsRunningThread返回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,接收者线程必须有一个事件循环);
return target;
}
}
#endif

metacall发布函数,在最简单的形式中,需要函子调用消费者为给定的线程提供对象,并实例化函子调用事件。事件的实现仍然在我们之前,并且是各种实现之间的本质区别。



第二个重载需要函子的右值引用,潜在地保存副本操作函数。如果连续包含复制代价昂贵的数据,这将非常有用。

  #ifndef HAS_POSTMETACALL 
void postMetaCall(QThread * thread,const std :: function< void()>& fun){
auto receiver = FunctorCallConsumer :: forThread
QCoreApplication :: postEvent(receiver,new FunctorCallEvent(fun,receiver));
}

void postMetaCall(QThread * thread,std :: function< void()>&& fun){
auto receiver = FunctorCallConsumer :: forThread );
QCoreApplication :: postEvent(receiver,
new FunctorCallEvent(std :: move(fun),receiver));
}
#endif

为了演示的目的, metacall到主线程,然后延迟到 QThread :: run()以启动一个事件循环来监听来自其他线程的可能的metacall。互斥体被用来允许线程用户以简单的方式等待线程开始,如果消费者的实现所需要的话。这种等待对上面给出的默认事件消费者是必要的。

  class Worker:public QThread {
QMutex m_started;
void run(){
m_started.unlock();
postMetaCall(qApp-> thread(),[] {
qDebug()<<工作函子在线程中执行 });
QThread :: run();
}
public:
Worker(QObject * parent = 0):QThread(parent){m_started.lock(); }
〜Worker(){quit();等待(); }
void waitForStart(){m_started.lock(); m_started.unlock(); }
};

最后,我们启动上面的工作线程,向主(应用)线程发布metacall,应用程序线程将一个metacall发布到工作线程。

  int main(int argc,char * argv [])
{
QCoreApplication a(argc, argv);
a.thread() - > setObjectName(main);
工人工人;
worker.setObjectName(worker);
qDebug()<< worker thread:< & worker;
qDebug()<< main thread:<< QThread :: currentThread();
if(FunctorCallConsumer :: needsRunningThread()){
worker.start();
worker.waitForStart();
}
postMetaCall(& worker,[] {qDebug()<<main functor在线程中执行 if(!FunctorCallConsumer :: needsRunningThread())worker.start();
QMetaObject :: invokeMethod(& a,quit,Qt :: QueuedConnection);
return a.exec();
}

在所有实现中,输出将近似如下。函数跨越线程:在主线程中创建的函数在工作线程中执行,反之亦然。

  worker线程:QThread(0x7fff5692fc20,name =worker)
主线程:QThread(0x7f86abc02f00,name =main)
主函数在线程中执行QThread(0x7fff5692fc20,name =worker)
worker functor在线程中执行QThread(0x7f86abc02f00,name =main)



一个临时对象作为信号源



Qt 5最简单的方法是使用临时 QObject 源,并将函数连接到其 destroyed(QObject *)信号。当 postMetaCall 返回时, signalSource 被销毁,发出销毁 signal,并将metacall发布到代理对象。



这可能是C ++ 11风格中最简洁和直接的实现。 signalSource 对象以C ++ 11 RAII方式用于其销毁的副作用。短语副作用在C ++ 11的语义中具有意义,不应该被解释为意味着不可靠或不期望的 - 这是什么。 QObject 与我们的合同是在其析构函数执行期间有时释放销毁。我们非常欢迎使用这个事实。

  #include< QCoreApplication> 
#include< QThread>
#include< QAbstractEventDispatcher>
#include< QDebug>
#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();}
}
//此后的公共代码

如果我们只打算发布到主线程,代码变得几乎无关紧要:

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



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



同样的方法可以直接应用到 QEvent 。事件的虚拟析构函数可以调用函子。事件在由消费者对象的线程的事件分派器传递之后被删除,因此它们总是在正确的线程中执行。这在Qt 4/5中不会改变,并且在Qt 6中也不会改变。

  #include< QCoreApplication> ; 
#include< QThread>
#include< QEvent>
#include< QAbstractEventDispatcher>
#include< QDebug>
#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 (QEvent :: None),
QEvent(QEvent :: None),其中,QEvent() m_fun(std :: move(fun)),m_thread(receiver-> thread()){qDebug()< 移动语义; }
〜FunctorCallEvent(){
if(QThread :: currentThread()== m_thread)
m_fun();
else
qWarning()<< 删除指向线程的函子调用< m_thread;
}
};
//此后的公共代码

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

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



Qt 5解决方案使用Private QMetaCallEvent



函数可以包装在 QMetaCallEvent 的Qt 5槽对象有效载荷中。函数将由 QObject: :event ,因此可以被发布到目标线程中的任何对象。此解决方案使用Qt 5的私有实现细节。

  #include< QCoreApplication> 
#include< QThread>
#include< QAbstractEventDispatcher>
#include< QDebug>
#include < private / qobject_p.h>
#include< functional>

class FunctorCallEvent:public QMetaCallEvent {
public:
template&
FunctorCallEvent(const Functor& fun,QObject * receiver):
QMetaCallEvent(new QtPrivate :: QFunctorSlotObject< Functor,0,typename QtPrivate :: List_Left< void,0> :: Value,void& func),
receiver,0,0,0,(void **)malloc(sizeof(void *))){}
//带槽对象的Metacalls需要一个参数数组作为返回类型,即使它是无效的。
};
//此后的常用代码



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



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

  #include< QCoreApplication> 
#include< QThread>
#include< QEvent>
#include< QMap>
#include< QMutex>
#include< QDebug>
#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(fun){qDebug()< 移动语义; }
void call(){m_fun(); }
};

#define HAS_FUNCTORCALLCONSUMER
class FunctorCallConsumer:public QObject {
typedef QMap< QThread *,FunctorCallConsumer *>地图;
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()< 消费者为线程做<线();
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;
//此后的公共代码


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

or:

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

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

How can I do the same in Qt?

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.

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.

TL;DR for functors

// Qt 5/4 - preferred, has least allocations
template <typename F>
static void postToThread(F && fun, QObject * obj = qApp) {
  struct Event : public QEvent {
    F fun;
    Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
    ~Event() { fun(); }
  };
  QCoreApplication::postEvent(obj, new Event(std::move(fun)));
}

// Qt 5 - alternative version
template <typename F>
static void postToThread(F && fun, QObject * obj = qApp) {
   QObject src;
   QObject::connect(&src, &QObject::destroyed, obj, std::move(fun), Qt::QueuedConnection);
}

QThread t;
QObject o;
o.moveToThread(&t);

// Execute in given object's thread
postToThread([&]{ o.setObjectName("hello"); }, &o);
// or
postToThread(std::bind(&QObject::setObjectName, &o, "hello"), &o);

// Execute in the main thread
postToThread([]{ qDebug() << "hello"; });

TL;DR for methods/slots

// Qt 5/4
template <typename T, typename R>
static void postToThread(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)(); }
  };
  QCoreApplication::postEvent(obj, new Event(obj, method));
}

QThread t;
struct MyObject : QObject { void method() {} } obj;
obj.moveToThread(&t);

// Execute in obj's thread
postToThread(&obj, &MyObject::method);

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

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 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.

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 <QCoreApplication>
#include <QThread>
#include <QAbstractEventDispatcher>
#include <QDebug>
#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(); });
}
// 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();
  });
}

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, and is unlikely to change in Qt 6 as well.

#include <QCoreApplication>
#include <QThread>
#include <QEvent>
#include <QAbstractEventDispatcher>
#include <QDebug>
#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)));
}

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 <QCoreApplication>
#include <QThread>
#include <QAbstractEventDispatcher>
#include <QDebug>
#include <private/qobject_p.h>
#include <functional>

class FunctorCallEvent : public QMetaCallEvent {
public:
   template <typename Functor>
   FunctorCallEvent(const Functor & fun, QObject * receiver) :
      QMetaCallEvent(new QtPrivate::QFunctorSlotObject<Functor, 0, typename QtPrivate::List_Left<void, 0>::Value, void>(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 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 <QCoreApplication>
#include <QThread>
#include <QEvent>
#include <QMap>
#include <QMutex>
#include <QDebug>
#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(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风格的给定线程中执行函子?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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