Maison >développement back-end >C++ >Comment exécuter des foncteurs ou des Lambdas dans un thread donné dans Qt, similaire à GCD ?
En ObjC avec GCD, il existe un moyen d'exécuter un lambda dans l'un des threads qui font tourner une boucle d'événements. Par exemple :
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
ou :
dispatch_async(dispatch_get_main_queue(), ^{ /* do sth */ });
Il exécute quelque chose (équivalent à []{ / do sth / } en C ) dans le thread principal file d'attente, bloquante ou asynchrone.
Comment puis-je faire la même chose dans Qt ?
D'après ce que j'ai lu, je suppose que la solution serait d'une manière ou d'une autre d'envoyer un signal à un objet du thread principal. Mais quel objet ? Juste QApplication::instance() ? (C'est le seul objet vivant dans le fil principal à ce stade.) Et quel signal ?
C'est certainement possible. Toute solution se concentrera sur la fourniture d'un événement qui enveloppe le foncteur vers un objet consommateur résidant dans le thread souhaité. Nous appellerons cette opération "metacall posting". Les détails peuvent être exécutés de plusieurs manières.
// 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); }
Toutes les méthodes ci-dessus fonctionnent à partir de threads qui n'ont pas de boucle d'événements. En raison de QTBUG-66458, l'appropriation pratique de QTimer::singleShot nécessite également une boucle d'événement dans le thread source. Ensuite, postToObject devient très simple, et vous pouvez éventuellement simplement utiliser QTimer::singleShot directement, bien que ce soit un nom maladroit qui cache l'intention à ceux qui ne connaissent pas cet idiome. L'indirection via une fonction nommée pour mieux indiquer l'intention a du sens, même si vous n'avez pas besoin de la vérification de type :
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)); }
Définissons notre problème en termes de suivant le code commun. Les solutions les plus simples publieront l'événement soit sur l'objet d'application, si le thread cible est le thread principal, soit sur un répartiteur d'événements pour tout autre thread donné. Étant donné que le répartiteur d'événements n'existera qu'après la saisie de QThread::run, nous indiquons la nécessité pour le thread d'être en cours d'exécution en renvoyant true à partir de needRunningThread.
#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
Les fonctions de publication de méta-appels, dans leur forme la plus simple , exige que le consommateur de l'appel du foncteur fournisse un objet pour un thread donné et instancie l'événement d'appel du foncteur. La mise en œuvre de l'événement est encore devant nous et constitue la différence essentielle entre les différentes implémentations.
#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
À des fins de démonstration, le thread de travail publie d'abord un méta-appel au thread principal, puis s'en remet à QThread. ::run() pour démarrer une boucle d'événements afin d'écouter d'éventuels méta-appels provenant d'autres threads. Un mutex est utilisé pour permettre à l'utilisateur du thread d'attendre de manière simple que le thread démarre, si l'implémentation du consommateur l'exige. Une telle attente est nécessaire pour le consommateur d'événements par défaut indiqué ci-dessus.
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!