c++ multithreading qt

c++ - ¿Cómo ejecutar un functor o una lambda en un hilo dado en Qt, estilo GCD?



qthread exec (4)

En ObjC con GCD, hay una forma de ejecutar un lambda en cualquiera de los hilos que hacen girar un bucle de evento. Por ejemplo:

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

o:

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

Ejecuta algo (equivalente a []{ /* do sth */ } en C ++) en la cola del hilo principal, ya sea de bloqueo o asincrónicamente.

¿Cómo puedo hacer lo mismo en Qt?

Por lo que he leído, creo que la solución sería de alguna manera enviar una señal a algún objeto del hilo principal. Pero, ¿qué objeto? Solo QApplication::instance() ? (Ese es el único objeto que vive en el hilo principal en ese punto). ¿Y qué señal?

A partir de las respuestas actuales y mi investigación actual, realmente parece que necesito un objeto ficticio para sentarme en el hilo principal con alguna ranura que simplemente espere para obtener algún código para ejecutar.

Entonces, decidí subclasificar QApplication para agregar eso. Mi código actual, que no funciona (pero tal vez puedas ayudar):

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


¿Puede algo como esto ser útil?

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

Este fragmento de código hará que su lambda se ejecute en el bucle de evento de hilo principal tan pronto como sea posible. Sin soporte args, este es un código muy básico.

NOTA: No lo probé correctamente.


Ciertamente es posible. Cualquier solución se centrará en la entrega de un evento que envuelva el functor a un objeto de consumo que resida en el hilo deseado. Llamaremos a esta operación publicación de metacall. Los detalles se pueden ejecutar de varias maneras.

Qt 5.10 y up TL; DR

// 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 para funtores

// https://github.com/KubaO/n/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 para métodos / ranuras

// 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: ¿Qué tal un temporizador de disparo único?

Todos los métodos anteriores funcionan a partir de subprocesos que no tienen un bucle de evento. Debido a QTBUG-66458 , la práctica asignación de QTimer::singleShot necesita un bucle de evento en el hilo fuente. Entonces, postToObject vuelve muy simple, y posiblemente solo QTimer::singleShot usar QTimer::singleShot directamente, aunque es un nombre incómodo que oculta la intención de aquellos que no están familiarizados con este modismo. La indirección a través de una función nombrada para indicar mejor el intento tiene sentido, incluso si no necesita la verificación de tipo:

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

Código común

Definamos nuestro problema en términos del siguiente código común. Las soluciones más simples publicarán el evento en el objeto de la aplicación, si el hilo objetivo es el hilo principal, o en un despachador de eventos para cualquier otro hilo dado. Dado que el despachador de eventos existirá solo después de que se haya ingresado QThread::run , indicamos el requisito de que el hilo se ejecute al devolver true de 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

Las funciones de publicación de metacall, en su forma más simple, requieren que el consumidor de la llamada del functor proporcione el objeto para un subproceso dado y crea una instancia del evento de llamada del functor. La implementación del evento aún está por delante de nosotros, y es la diferencia esencial entre varias implementaciones.

La segunda sobrecarga toma una referencia rvalue para el funtor, lo que permite guardar una operación de copia en el funtor. Esto es útil si la continuación contiene datos que son costosos de copiar.

#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

Para fines de demostración, el subproceso de trabajador primero publica una metacall en el subproceso principal, y luego difiere a QThread::run() para iniciar un bucle de evento para escuchar posibles metacalls de otros subprocesos. Se utiliza un mutex para permitir que el usuario del subproceso espere de manera simple para que el subproceso se inicie, si es necesario por la implementación del consumidor. Tal espera es necesaria para el consumidor de evento predeterminado dado anteriormente.

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

Finalmente, iniciamos el subproceso de trabajo anterior que publica una metacall en el subproceso principal (aplicación) y el subproceso de aplicación publica una metacall en el subproceso de trabajo.

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

La salida se verá aproximadamente como sigue en todas las implementaciones. Los funtores cruzan los hilos: el creado en el hilo principal se ejecuta en el hilo de trabajo, y viceversa.

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

Solución Qt 5 utilizando un objeto temporal como fuente de señal

El enfoque más simple para Qt 5 es usar un QObject temporal como fuente de señal y conectar el funtor a su señal destroyed(QObject*) . Cuando devuelve postMetaCall , signalSource se destruye, emite su señal destroyed y publica el metacall en el objeto proxy.

Esta es quizás la implementación más concisa y directa en el estilo C ++ 11. El objeto signalSource se utiliza en la moda C ++ 11 RAII para los efectos secundarios de su destrucción. La frase "efectos secundarios" tiene un significado dentro de la semántica de C ++ 11 y no debe interpretarse como que significa "poco confiable" o "indeseable", es todo lo contrario. El contrato de QObject con nosotros es emitir destroyed algún momento durante la ejecución de su destructor. Somos más que bienvenidos para usar ese hecho.

#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

Si solo tenemos la intención de publicar en el hilo principal, el código se vuelve casi 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

Solución Qt 4/5 utilizando QEvent Destructor

El mismo enfoque se puede aplicar a QEvent directamente. El destructor virtual del evento puede llamar al functor. Los eventos se eliminan inmediatamente después de ser entregados por el despachador de eventos del subproceso del objeto del consumidor, por lo que siempre se ejecutan en el subproceso correcto. Esto no cambiará en 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

Para publicar solo en el hilo principal, las cosas se vuelven aún más simples:

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 usando el QMetaCallEvent privado

El functor se puede envolver en la carga de objetos de la ranura Qt 5 del QMetaCallEvent . El functor será invocado por QObject::event , y por lo tanto se puede publicar en cualquier objeto en el hilo de destino. Esta solución usa los detalles privados de implementación de 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 Solución usando un evento personalizado y consumidor

Reimplementamos el método de event() del objeto y lo hacemos llamar al funtor. Esto requiere un objeto de consumidor de evento explícito en cada subproceso en el que se contabilizan los funtores. El objeto se limpia cuando termina el hilo o, para el hilo principal, cuando se destruye la instancia de la aplicación. Funciona tanto en Qt 4 como en Qt 5. El uso de referencias de valores r evita la copia del funtor temporal.

#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


Creo que hay un nuevo enfoque que es el más fácil. Es de Qt 5.4. Enlace a la documentación

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

Ejemplo:

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

lambda se ejecutará en el subproceso qApp (hilo principal). Puede reemplazar el contexto con cualquier QObject que desee.

Actualizado

QTimer necesita que el bucle de eventos funcione. Para hilos sin bucle de evento qt (std :: hilo) podríamos crear uno. Código para ejecutar lambda en std :: thread.

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

Ejemplo completo

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


No tengo idea de qué estás hablando, pero intentaré responder de cualquier forma.

Digamos que tienes una clase con un juego de tragamonedas

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

Entonces, no, si desea ejecutar esto sincrónicamente en el hilo principal, puede llamar directamente a esa función. Para conectar una señal, necesitas crear un objeto y conectar una señal a la ranura.

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

Y en algún lugar del hilo principal haces algo así como

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

Entonces, si algo puede conectar múltiples señales a ese objeto de ranura, pero de manera realista, el código que proporcioné no ejecutará ninguna llamada diferente a una función normal. Podrás ver eso con un rastro de pila. Si agrega la bandera qobject :: queuedConneciton a la conexión, la llamada de la ranura se ejecutará en el bucle de evento.

También puede enhebrar fácilmente una señal, pero esto será automáticamente una conexión en cola

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

Ahora tendrás una señal de rosca básicamente. Si su señal va a ser enhebrada, todo lo que tiene que hacer es cambiar a signalClass.moveToThread (y someThread), cuando se emita la señal, la clase se ejecutará en el hilo principal.

Si no quiere que se llame un objeto, no estoy seguro, las lamdas podrían funcionar. Los he usado antes, pero creo que todavía necesitan estar envueltos en una clase.

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

Aunque estoy bastante seguro de que con Qt5 puedes incluso llegar a crear una ranura en línea dentro de una conexión. Pero una vez que usas lambdas no tengo idea de cómo funcionan los hilos con ellos. Por lo que yo sé, necesitas un objeto para sentarse en un hilo, básicamente, para obligar a llamar a la ranura desde el hilo principal.