After reading some articles like this about Qt Signal-Slot communications I still have a question concerning the queued connection.
If I have some threads sending signals all the time to each other and lets say one thread_slow
is running a slow method in it's event loop and another thread_fast
is running a fast one that sends multiple signals while the other thread is still running it's slow method.....when the slow method from thread_slow
returns to the event loop, will it process all the signals that were sent before by thread_fast
or just the last one (all the signals are the same type)?
If it will process all the signals, is it there a way to make the thread_slow
only process the last one? (Considering "the last one" in a multithread application might be vague, let's consider the last signal before the thread asked for the last signal, for the sake of simplicity, so new ones being sent while the thread looks for the last might be lost).
(I am asking this because I have multiple threads receiving data from multiple threads, and I dont want them to process old data, just the last one that was sent)
I have run some tests, and it appears that Qt will process all the signals. I made one thread do:
while(true)
{
QThread::msleep(500);
emit testQueue(test);
test++;
}
and a slot in another will do:
void test::testQueue(int test)
{
test.store(private_test.load() + test);
emit testText(QString("Test Queue: ") + QString::number(private_test.load()));
}
and the thread will run:
while(true)
{
QThread::msleep(3000);
QCoreApplication::processEvents();
private_test.store(private_test.load() + 1000);
}
I am sending a signal from one thread to the other every 500 milliseconds, and the other thread sleeps for 3000 milliseconds (3 seconds) and then wakes up and increment an internal variable by 100. every time the slot is executed it emits a text with the value received + the internal variable. The result I am having is that every time QCoreApplication::processEvents();
is called, all signals are executed....(I edited this part because I found a bug in my previous code)
I am trying to form my comment into an answer. I agree with you about that the documentation is lacking this information, or at least it is not clear for me, and apparently for you either.
There would be two options to get more information:
1) Trial
Put a qDebug() or printf()/fprintf() statement into your slot in the "slow" thread and see what it prints out. Run this a few times and draw the conclusion.
2) Making sure
You would need to read the source code for this how the meta object compiler, aka. moc gets this through from the source file. This is a bit more involved investigation, but this could lead to certainity.
As far as I know, every signal emission posting a corresponding event. Then, the event will be queued for the separate thread within the thread class. Here you can find the relevant two source code files:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
and
class QPostEventList : public QVector
There are two approaches with their trade-offs:
The main advantage is that signals could not be lost during the busy operation. However, this could be inherently slower as it can potentially process a lot more operation than needed.
The idea is that the data is re-set for each event handled, but the real busy operation is queued for execution only once. It does not necessarily have to be the for the first event if there are more, but that is the simplest implementation.
Foo::Foo(QObject *parent) : QObject(parent)
{
...
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
connect(this, SIGNAL(queueBusyOperationSignal()), SLOT(busyOperation()));
...
}
void Foo::dataUpdateSlot(const QByteArray &data)
{
m_data = data;
if (busyOperationQueued);
emit queueBusyOperationSignal();
m_busyOperationQueued = true;
}
}
void MyClass::busyOperationSlot()
{
// Do the busy work with m_data here
m_busyOperationQueued = false;
}
The idea is to disconnect the slot from the corresponding signal when starting the processing. This will ensure that new signal emission would not be caught, and connect the slot to the signal again once the thread is free to process the next events.
This would have some idle time in the thread though between the connection and the next even handled, but at least this would be a simple way of implmeneting it. It may actually be even negligible a performance difference depending on more context not really provided here.
The main drawback is that this would lose the signals during the busy operation.
Foo::Foo(QObject *parent) : QObject(parent)
{
...
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(busyOperationSlot(const QByteArray&)));
...
}
void MyClass::busyOperationSlot(const QByteArray &data)
{
disconnect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), this, SLOT(dataUpdateSlot(const QByteArray&)));
// Do the busy work with data here
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
}
I was thinking if there was a convenient API - e.g. a processEvents() alike method, but with an argument to process only the last event posted - for actually telling the event system explicitly to process the last one rather than circumventing the issue itself. It does appear to be such an API, however, it is private.
Perhaps, someone will submit a feature request to have something like that in public.
/*!
\internal
Returns \c true if \a event was compressed away (possibly deleted) and should not be added to the list.
*/
bool QCoreApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents)
The relevant source code can be found here.
It also seems to have an overriden version in QGuiApplication
and QApplication
.
As for completeness, there is also such a method like this:
void QCoreApplication::removePostedEvents(QObject * receiver, int eventType = 0) [static]
Removes all events of the given eventType that were posted using postEvent() for receiver.
The events are not dispatched, instead they are removed from the queue. You should never need to call this function. If you do call it, be aware that killing events may cause receiver to break one or more invariants.
If receiver is null, the events of eventType are removed for all objects. If eventType is 0, all the events are removed for receiver. You should never call this function with eventType of 0. If you do call it in this way, be aware that killing events may cause receiver to break one or more invariants.
But this is not quite what you would like to have here as per documentation.