Maison >développement back-end >C++ >Comment exécuter des foncteurs ou des Lambdas dans un thread donné dans Qt, similaire à GCD ?

Comment exécuter des foncteurs ou des Lambdas dans un thread donné dans Qt, similaire à GCD ?

DDD
DDDoriginal
2024-12-20 04:37:13153parcourir

How to Execute Functors or Lambdas in a Given Thread in Qt, Similar to GCD?

Comment exécuter un foncteur ou un lambda dans un thread donné en Qt, style 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 ?

Solution

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.

Qt 5.10 & 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 pour les foncteurs

// 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 &amp;&amp;fun, QObject *obj = qApp) {
  QMetaObject::invokeMethod(obj, std::forward<F>(fun));
}

template <typename F>
static void postToThread(F &amp;&amp; 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 &amp;&amp; fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
   FEvent(const Fun &amp; fun) : QEvent(QEvent::None), fun(fun) {}
   ~FEvent() { fun(); }
}; }

template <typename F>
static void postToObject(F &amp;&amp; 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 &amp;&amp; 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(&amp;t);

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

   // Execute in given thread
   postToThread([]{ qDebug() << "hello from worker thread"; });

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

TL; DR pour les méthodes/emplacements

// 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(&amp;t);

   // Execute in obj's thread
   postToObject(&amp;obj, &amp;MyObject::method);
}

Qu'en est-il d'un seul coup timer ?

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

Code commun

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()> &amp; fun) {
   auto receiver = FunctorCallConsumer::forThread(thread);
   QCoreApplication::postEvent(receiver, new FunctorCallEvent(fun, receiver));
}

void postMetaCall(QThread * thread, std::function<void()> &amp;&amp; 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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn