如何在Qt,GCD风格的给定线程中执行函数或lambda?

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

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

要么:

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

它在主线程的队列中执行某些操作(相当于C ++中的[]{ /* do sth */ } ),可以是阻塞的,也可以是asynchronous的。

我怎样才能在Qt中做同样的事情?

从我读过的内容来看,我想这个解决scheme会以某种方式向主线程的某个对象发送一个信号。 但是什么对象? 只是QApplication::instance() ? (那是生活在主线程中的唯一对象)。什么信号?


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

所以我决定QApplication 。 我目前的代码,这是行不通的(但也许你可以帮助):

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

这当然是可能的。 任何解决scheme都将集中于将函子包装的事件传递给驻留在所需线程中的消费者对象。 我们将这个操作称为metacall张贴。 这些细节可以通过几种方式执行。

TL;仿函子的DR

 // Qt 5/4 - preferred, has least allocations template <typename F> static void postToThread(F && fun, QObject * obj = qApp) { struct Event : public QEvent { using Fun = typename std::decay<F>::type; Fun fun; Event(Fun && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} Event(const Fun & fun) : QEvent(QEvent::None), fun(fun) {} ~Event() { fun(); } }; QCoreApplication::postEvent(obj, new Event(std::forward<F>(fun))); } // Qt 5 - alternative version template <typename F> static void postToThread2(F && fun, QObject * obj = qApp) { 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 postToThread([&]{ o.setObjectName("hello"); }, &o); // or postToThread(std::bind(&QObject::setObjectName, &o, "hello"), &o); // Execute in the main thread 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)(); Event(T * obj, R(T::*method)()): QEvent(QEvent::None), obj(obj), method(method) {} ~Event() { (obj->*method)(); } }; 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 postToThread(&obj, &MyObject::method); } 

通用代码

我们用下面的通用代码来定义我们的问题。 最简单的解决scheme将事件发布到应用程序对象(如果目标线程是主线程),或发送到任何其他给定线程的事件分派器。 由于事件分派器只有在QThread::run被input之后才会存在,我们通过从needsRunningThread返回true来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 

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

第二个重载需要一个函子的右值引用,可能会在仿函数上保存一个复制操作。 如果继续包含复制的代价昂贵的数据,这将非常有用。

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

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

最后,我们开始上面的工作线程向主(应用)线程发送一个元调用,并且应用程序线程向工作线程发送一个元调用。

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

在所有实现中,输出大致如下所示。 函子穿过线程:在主线程中创build的函数在工作线程中执行,反之亦然。

 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解决scheme使用临时对象作为信号源

对于Qt 5来说,最简单的方法是使用一个临时的QObject作为信号源,并将函子连接到其destroyed(QObject*)信号。 当postMetaCall返回时, signalSource被析构,发出其destroyed信号,并将metacall传递给代理对象。

这也许是C ++ 11风格中最简洁直接的实现。 signalSource对象以C ++ 11 RAII方式用于其销毁的副作用。 “副作用”一词在C ++ 11的语义中有其含义,不应该被解释为“不可靠的”或“不可取的” – 这是什么意思。 QObject与我们的合同是在执行析构函数期间的某个时候被销毁。 我们非常欢迎使用这个事实。

 #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 

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

 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 

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

同样的方法可以直接应用于QEvent 。 事件的虚拟析构函数可以调用函子。 这些事件被消费者对象的线程的事件调度器传递之后被删除,所以它们总是在正确的线程中执行。 这在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 

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

 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解决scheme使用私人QMetaCallEvent

函数可以包装在QMetaCallEvent的Qt 5插槽对象有效载荷中。 函数将被QObject::event调用,因此可以被发布到目标线程中的任何对象。 该解决scheme使用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解决scheme使用自定义事件和消费者

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

 #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 

可能这样的事情有用吗?

 template <typename Func> inline static void MyRunLater(Func func) { QTimer *t = new QTimer(); t->moveToThread(qApp->thread()); t->setSingleShot(true); QObject::connect(t, &QTimer::timeout, [=]() { func(); t->deleteLater(); }); QMetaObject::invokeMethod(t, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } 

这段代码将尽可能快地让你的lambda在主线程事件循环中运行。 没有参数的支持,这是一个非常基本的代码。

注:我没有正确testing。

有一种新的方法是我认为最简单的方法。 它来自Qt 5.4。 链接到文档

 void QTimer::singleShot(int msec, const QObject *context, Functor functor) 

例:

 QTimer::singleShot(0, qApp, []() { qDebug() << "hi from event loop"; }); 

lambda将在qApp线程(主线程)中执行。 你可以用你想要的任何QObjectreplace上下文。

更新

QTimer需要事件循环才能工作。 对于没有qt事件循环(std :: thread)的线程,我们可以创build一个。 在std :: thread中运行lambda的代码。

 QEventLoop loop; Q_UNUSED(loop) QTimer::singleShot(0, qApp, []() { qDebug() << "singleShot from std thread"; }); 

完整的例子

 #include <QCoreApplication> #include <QTimer> #include <QDebug> #include <thread> #include <QThread> #include <QEventLoop> #include <QThread> using std::thread; class TestObj :public QObject { // Used new connect syntax no need for Q_OBJECT define // you SHOULD use it. I used just to upload one file //Q_OBJECT public slots: void doWork() { qDebug() << "QThread id" << QThread::currentThreadId(); QTimer::singleShot(0, qApp, []() { qDebug() << "singleShot from QThread" << QThread::currentThreadId(); }); } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "main thread id" << QThread::currentThreadId(); thread testThread([]() { QEventLoop loop; Q_UNUSED(loop) qDebug() << "std::thread id" << QThread::currentThreadId(); QTimer::singleShot(0, qApp, []() { qDebug() << "singleShot from std thread" << QThread::currentThreadId(); }); qDebug() << "std::thread finished"; }); testThread.detach(); QThread testQThread; TestObj testObj; testObj.moveToThread(&testQThread); QObject::connect(&testQThread, &QThread::started, &testObj, &TestObj::doWork); testQThread.start(); return a.exec(); } 

我完全不知道你在说什么,但我会尽力回答。

比方说你有一个插槽function的类

 class MyClass : public QObject { Q_OBJECT public: MyClass() {} public slots: void MySlot() { qDebug() << "RAWR"; }; 

所以如果你想在主线程中同步运行,你可以直接调用这个函数。 为了连接信号,您需要创build一个对象并将信号连接到插槽。

 class MySignalClass : public QObject { Q_OBJECT public: MySignalClass() {} signalSomthign() { emit someAwesomeSignal; } public signals: void someAwesomeSignal(); }; 

而且,在主线程中,你可以做类似的事情

 MyClass slotClass; MySignalClass signalClass; qobject::connect(&signalClass, SIGNAL(someAwesomeSignal), &slotClass(), SLOT(MySlot))); 

所以现在如果你可以将多个信号连接到这个槽对象,但是实际上我提供的代码不会和普通的函数调用不同。 你将能够看到一个堆栈跟踪。 如果你添加标志qobject :: queuedConneciton到连接,那么它将在事件循环中调度插槽调用。

你也可以很容易的线程化一个信号,但是这会自动成为一个排队连接

 MyClass slotClass; MySignalClass signalClass; QThread someThread; slotClass.moveToThread(&someThread); qobject::connect(&signalClass, SIGNAL(someAwesomeSignal), &slotClass(), SLOT(MySlot))); 

现在你基本上会有一个线程信号。 如果你的信号将被线程化,那么你所要做的就是切换到signalClass.moveToThread(&someThread),当信号发射signalClass将在主线程中运行。

如果你不想要一个对象被调用,我不确定,lamdas可能会工作。 我以前用过它们,但是我还是需要把它们包装在一个class里。

 qobject::connect(&signalClass, &slotClass::MySlot, [=]() { /* whatever */ }); 

虽然我非常确定Qt5,但你甚至可以在连接中创build一个插槽。 但是,一旦你使用lambdas我不知道线程如何与他们合作。 据我所知你需要一个对象坐在一个线程基本上强制从主线程插槽。