9

There are two objects in my program. One object is emitting a signal. The other one receives the signal in a slot and processes the incoming signals one by one. Both objects are running in different threads. Now I need to measure and monitor the workload for my receiving object.

The problem is I do not know how many signals are waiting for my second object to process in the Qt signal queue. Is there a way to get the size of this queue? Or is there a work around to know how many signals have to be still proecessed?

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
Ryan
  • 125
  • 1
  • 7
  • 1
    You could write a "monitor" object, connect the sender's signal with Qt::DirectConnection with the monitor (the monitor lives in the receiver's thread!), have the monitor increment a counter, emit the signal again, connected to itself using a Qt::QueuedConnection, and in the corresponding slot decrement the counter (using QAtomicInt). Then emit a final time, and connect that signal with the receiver (this emission will be direct, since both objects are in the same thread). Any time then you can ask the monitor for the pending events. – Johannes Schaub - litb Jun 08 '17 at 16:21
  • The receiver and the monitor can also be the same object, if you want to keep things tight (the final emission can then be omitted). Qt5 may provide an easier way though, I haven't done anything with it yet. – Johannes Schaub - litb Jun 08 '17 at 16:23
  • An alternative could be to give the signal an additional parameter (a shared pointer for example), which for its lifetime will keep a counter incremented, and decrement it when it dies. Since the event queue needs to keep a copy of the signal arguments, this would work aswell, I think (not tested!). – Johannes Schaub - litb Jun 08 '17 at 16:26
  • In general for a 1:1 producer-consumer scenario, I would use a list, rather than signals though: The producer puts its item into a QList (protected with a mutex), and if it becomes non-empty, calls a Q_INVOKABLE function "processList" on the consumer (using queued-connection and QMetaObject). In that function, the mutex is locked and the list processed. That way you can always inspect the number of items not yet processed, and potentially reduce the number of events sent. And still are notified asynchronously. – Johannes Schaub - litb Jun 08 '17 at 16:51

1 Answers1

5

The qGlobalPostedEventsCount() is a starting point, although it only works for the current thread.

To poll an arbitrary thread, we can to use Qt's internals. The implementation is then very simple. It works even when the thread is blocked and doesn't process events.

// https://github.com/KubaO/stackoverflown/tree/master/questions/queue-poll-44440584
#include <QtCore>
#include <private/qthread_p.h>
#include <climits>

uint postedEventsCountForThread(QThread * thread) {
   if (!thread)
      return -1;
   auto threadData = QThreadData::get2(thread);
   QMutexLocker lock(&threadData->postEventList.mutex);
   return threadData->postEventList.size() - threadData->postEventList.startOffset;
}

uint postedEventsCountFor(QObject * target) {
   return postedEventsCountForThread(target->thread());
}

If one really wishes not to use private APIs, we can have a less straightforward solution with more overhead. First, let's recall that the lowest overhead means of "doing stuff in some object's thread" is to do said "stuff" in an event's destructor - see this answer for more details. We can post the highest priority event to the target object's event queue. The event wraps a task that invokes qGlobalPostedEventsCount, updates the count variable, and releases a mutex that we then acquire. At the time of mutex acquisition, the count has a valid value that is returned. If the target thread is unresponsive and the request times out, -1 is returned.

uint qGlobalPostedEventsCount(); // exported in Qt but not declared
uint postedEventsCountForPublic(QObject * target, int timeout = 1000) {
   uint count = -1;
   QMutex mutex;
   struct Event : QEvent {
      QMutex & mutex;
      QMutexLocker lock;
      uint & count;
      Event(QMutex & mutex, uint & count) :
         QEvent(QEvent::None), mutex(mutex), lock(&mutex), count(count) {}
      ~Event() {
         count = qGlobalPostedEventsCount();
      }
   };
   QCoreApplication::postEvent(target, new Event(mutex, count), INT_MAX);
   if (mutex.tryLock(timeout)) {
      mutex.unlock();
      return count;
   }
   return -1;
}

And a test harness:

int main(int argc, char ** argv) {
   QCoreApplication app(argc, argv);
   struct Receiver : QObject {
      bool event(QEvent *event) override {
         if (event->type() == QEvent::User)
            QThread::currentThread()->quit();
         return QObject::event(event);
      }
   } obj;
   struct Thread : QThread {
      QMutex mutex;
      Thread() { mutex.lock(); }
      void run() override {
         QMutexLocker lock(&mutex);
         QThread::run();
      }
   } thread;
   thread.start();
   obj.moveToThread(&thread);
   QCoreApplication::postEvent(&obj, new QEvent(QEvent::None));
   QCoreApplication::postEvent(&obj, new QEvent(QEvent::None));
   QCoreApplication::postEvent(&obj, new QEvent(QEvent::None));
   QCoreApplication::postEvent(&obj, new QEvent(QEvent::User));
   auto count1 = postedEventsCountFor(&obj);
   thread.mutex.unlock();
   auto count2 = postedEventsCountForPublic(&obj);
   thread.wait();
   auto count3 = postedEventsCountFor(&obj);
   Q_ASSERT(count1 == 4);
   Q_ASSERT(count2 == 4);
   Q_ASSERT(count3 == 0);
}
QT = core-private
CONFIG += console c++11
CONFIG -= app_bundle
TARGET = queue-poll-44440584
TEMPLATE = app
SOURCES += main.cpp
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Is it allowed to include private headers in the lgpl linking model? – Johannes Schaub - litb Jun 09 '17 at 09:19
  • I don't know, but it's unnecessary. You're supposed to modify the copy of Qt that you're using so that the API is not private anymore, and thus the people you distribute stuff to can modify that API if they wish so and as long as their modifications retain BC and make sense, your code can be relinked and will work. – Kuba hasn't forgotten Monica Jun 09 '17 at 13:46
  • Is it still possible to be real time capable when using mutexes? Or is there a possiblity without mutexes also? – Ryan Jun 12 '17 at 08:18
  • There's a lot of mutexes in Qt you're synchronizing on without even knowing it. The locking of these mutexes won't affect your code at all - they are locked anyway every time you post an event to a given queue. Never mind the global allocator mutexes you eventually acquire when allocating memory. Qt is not real time capable by design, so you're raising a non-issue. – Kuba hasn't forgotten Monica Jun 12 '17 at 15:17
  • With larger event queues `if(mutex.tryLock(timeout))` from the second approach also needs more time. When the timeout occurs, I get the following error: `QMutex: destroying locked mutex` followed by an fatal assert error for example `"copy" in file thread\qmutex.cpp, line 595`. For me it solved it to make the mutex static. But I do not really understand why. – Ryan Jul 05 '17 at 08:35
  • #include this appear to not "exist" any idea where to find it? – Dariusz Nov 09 '19 at 07:04
  • 1
    @Dariusz It exists all right. You're not including the private parts of Qt modules in your project. Look at the example on github, get all the files (two of them), and you'll see that it builds. You can go from there. You're missing `QT += core-private` in the `.pro` file (and then you must re-run qmake manually or remove the build folder before it'll build correctly). – Kuba hasn't forgotten Monica Nov 10 '19 at 06:38
  • @Ryan There is a chance that the mutex object is destroyed (`postedEventsCountForPublic` returns after wait timed-out) before the event is processed. When the `Event` object finally gets processed some time later and then released, the error occurs because it tries to unlock a already-released mutex object. – guan boshen Oct 23 '20 at 07:51