Heim >Backend-Entwicklung >C++ >Wie führe ich Funktoren oder Lambdas in einem bestimmten Thread in Qt aus, ähnlich wie bei GCD?
In ObjC mit GCD gibt es eine Möglichkeit, ein Lambda auszuführen in einem der Threads, die eine Ereignisschleife drehen. Zum Beispiel:
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
oder:
dispatch_async(dispatch_get_main_queue(), ^{ /* do sth */ });
Es führt etwas aus (entspricht []{ /etwas tun / } in C) im Hauptthread Warteschlange, entweder blockierend oder asynchron.
Wie kann ich das Gleiche tun? Qt?
Nach dem, was ich gelesen habe, schätze ich, dass die Lösung irgendwie darin bestünde, ein Signal an ein Objekt des Hauptthreads zu senden. Aber welches Objekt? Nur QApplication::instance()? (Das ist zu diesem Zeitpunkt das einzige Objekt, das im Hauptthread lebt.) Und welches Signal?
Es ist durchaus möglich. Jede Lösung konzentriert sich auf die Bereitstellung eines Ereignisses, das den Funktor in ein Verbraucherobjekt einschließt, das sich im gewünschten Thread befindet. Wir werden diese Operation Metacall-Posting nennen. Die Angaben können auf verschiedene Arten ausgeführt werden.
// 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), []{ ... });
// 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))); }
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"; }); }
// 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); }
Alle oben genannten Methoden funktionieren mit Threads, die keine Ereignisschleife haben. Aufgrund von QTBUG-66458 erfordert die praktische Verwendung von QTimer::singleShot auch eine Ereignisschleife im Quellthread. Dann wird postToObject sehr einfach, und Sie könnten möglicherweise einfach QTimer::singleShot direkt verwenden, obwohl es ein umständlicher Name ist, der die Absicht für diejenigen verbirgt, die mit dieser Redewendung nicht vertraut sind. Die Umleitung über eine Funktion mit dem Namen, um die Absicht besser anzuzeigen, ist sinnvoll, auch wenn Sie die Typprüfung nicht benötigen:
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)); }
Lassen Sie uns unser Problem anhand von definieren Befolgen Sie den allgemeinen Code. Die einfachsten Lösungen posten das Ereignis entweder an das Anwendungsobjekt, wenn der Ziel-Thread der Haupt-Thread ist, oder an einen Event-Dispatcher für einen anderen gegebenen Thread. Da der Event-Dispatcher erst existiert, nachdem QThread::run eingegeben wurde, geben wir die Anforderung für die Ausführung des Threads an, indem wir „true“ von „needsRunningThread“ zurückgeben.
#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
Die Metacall-Posting-Funktionen in ihrer einfachsten Form erfordern, dass der Funktoraufrufkonsument ein Objekt für einen bestimmten Thread bereitstellt und das Funktoraufrufereignis instanziiert. Die Implementierung des Ereignisses liegt noch vor uns und ist der wesentliche Unterschied zwischen verschiedenen Implementierungen.
#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
Zu Demonstrationszwecken sendet der Arbeitsthread zunächst einen Metaaufruf an den Hauptthread und übergibt ihn dann an QThread ::run(), um eine Ereignisschleife zu starten, um auf mögliche Metaaufrufe von anderen Threads zu warten. Ein Mutex wird verwendet, um dem Thread-Benutzer auf einfache Weise zu ermöglichen, auf den Start des Threads zu warten, wenn dies durch die Implementierung des Verbrauchers erforderlich ist. Eine solche Wartezeit ist für den oben angegebenen Standard-Ereigniskonsumenten erforderlich.
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
Das obige ist der detaillierte Inhalt vonWie führe ich Funktoren oder Lambdas in einem bestimmten Thread in Qt aus, ähnlich wie bei GCD?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!