Home >Backend Development >C++ >How to Execute Functors or Lambdas in a Given Thread in Qt, Similar to GCD?

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

DDD
DDDOriginal
2024-12-20 04:37:13151browse

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

How to execute a functor or a lambda in a given thread in Qt, GCD-style?

In ObjC with GCD, there is a way of executing a lambda in any of the threads that spin an event loop. For example:

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

or:

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

It executes something (equivalent to []{ / do sth / } in C ) in the main thread's queue, either blocking or asynchronously.

How can I do the same in Qt?

From what I have read, I guess the solution would be somehow to send a signal to some object of the main thread. But what object? Just QApplication::instance()? (That is the only object living in the main thread at that point.) And what signal?

Solution

It is certainly possible. Any solution will center on delivering an event that wraps the functor to a consumer object residing in the desired thread. We shall call this operation metacall posting. The particulars can be executed in several ways.

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 for functors

// 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 for methods/slots

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

What about a single shot timer?

All of the above methods work from threads that don't have an event loop. Due to QTBUG-66458, the handy appropriation of QTimer::singleShot needs an event loop in the source thread as well. Then postToObject becomes very simple, and you could possibly just use QTimer::singleShot directly, although it's an awkward name that hides the intent from those unfamiliar with this idiom. The indirection via a function named to better indicate the intent makes sense, even if you don't need the type check:

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

Common Code

Let's define our problem in terms of the following common code. The simplest solutions will post the event to either the application object, iff the target thread is the main thread, or to an event dispatcher for any other given thread. Since the event dispatcher will exist only after QThread::run has been entered, we indicate the requirement for the thread to be running by returning true from 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

The metacall posting functions, in their simplest form, require the functor call consumer to provide object for a given thread, and instantiate the functor call event. The implementation of the event is still ahead of us, and is the essential difference between various implementations.

#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

For demonstration purposes, the worker thread first posts a metacall to the main thread, and then defers to QThread::run() to start an event loop to listen for possible metacalls from other threads. A mutex is used to allow the thread user to wait in a simple fashion for the thread to start, if necessitated by the consumer's implementation. Such wait is necessary for the default event consumer given above.

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

The above is the detailed content of How to Execute Functors or Lambdas in a Given Thread in Qt, Similar to GCD?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn