How to Compress Slot Calls When Using Queued Connection in Qt

How to Compress Slot Calls When Using Queued Connection in Qt?

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:

Queue a busy slot operation from the data mutator slot

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

Connect/Disconnect

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

Future thoughts

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.

Slot invocation order in Qt queued connections

The slots are executed in the emission order of their corresponding signals as of today.

What happens in the background is that the queued connection puts an event into the queue and that will get processed. This queue is basically a FIFO (first-in-first-out) as it currently is implemented.

Here you can find some details if you wish to check out the implementation yourself:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)

and

class QPostEventList : public QVector

However, I would personally suggest not to rely on this internal behavior as it is not documented and such, it can probably be subject for further changes without any notice.

Therefore, in my opinion, the more future proof way is to make explicit dependency management in your software if you need to rely on the other in some way. This is usually not a problem and even makes the code more explicit, thus more comprehensive.

Good Practice to pass arguments in queued connection

If you want a queued connection, you need to call connect with a 5. parameter Qt::QueuedConnection. Otherwise, you get a direct connection inside the thread where you sent the signal from. Edit: See Tobys comment below.

You must wrap a QVector<> into a typedef, otherwise registering will not work (bug? in Qt from the stoneage). Also do not use references to your typedef, will not work either.

Header

typedef struct {
int a;
int b;
} mystruct;

typedef QVector<mystruct> myvector;
Q_DECLARE_METATYPE(myvector);

Source

void MainWindow::test()
{
qRegisterMetaType<myvector>();

connect(this, SIGNAL(sigRec(myvector)), SLOT(slotRec(myvector)), Qt::QueuedConnection);
mystruct x = {1,2};
myvector v;
v.append(x);
emit sigRec(v);
}

void MainWindow::slotRec(myvector s)
{
}

Question about Qt slots and multiple calls

If you truly use function scope variables, then it shouldn't matter. Example:

class WheelSpinner : public QThread
{
Q_OBJECT;
public:
WheelSpinner( QObject* receiver, const char* slot )
{
connect( this, SIGNAL( valueChanged( int ) ), receiver, slot,
Qt::DirectConnect );
}

void run()
{
for ( int i = 0; i < 100000; ++i )
{
emit ( valueChanged( i ) );
}
}

public signals:
void valueChanged( int value );
};

class ProgressTracker : public QObject
{
Q_OBJECT;
public:
ProgressTracker() { }

public slots:
void updateProgress( int value )
{
// While in this function, "value" will always be the proper
// value corresponding to the signal that was emitted.
if ( value == 100000 )
{
// This will cause us to quit when the *first thread* that
// emits valueChanged with the value of 100000 gets to this point.
// Of course, other threads may get to this point also before the
// program manages to quit.
QApplication::quit();
}
}
};

int main( int argc, char **argv )
{
QApplication app( argc, argv );
ProgressTracker tracker;
WheelSpinner spinner1( &tracker, SLOT( updateProgress( int ) ) );
WheelSpinner spinner2( &tracker, SLOT( updateProgress( int ) ) );
WheelSpinner spinner3( &tracker, SLOT( updateProgress( int ) ) );

spinner1.run();
spinner2.run();
spinner3.run();

return ( app.exec() );
}

Qt: How do I catch signals from multple threads in a slot where all signals are queued

Does your QThread run the event loop? It has to do it to receive signals:

Queued Connection The slot is invoked when control returns to the
event loop
of the receiver's thread. The slot is executed in the
receiver's thread.

Basically queued connection works the following way:

  1. The originator issues a signal.
  2. Qt creates an event and posts it into the receiver event queue.
  3. The receiver goes through its event queue, picks up the events and dispatches the signals into the connected slots.

Hence if you do not run the event queue, the signals are posted but your thread will never receive them.

So basically your thread should do some initialization in run() but then call exec() and pass it to Qt.

If your thread also needs to run some periodic operations besides checking for signals, you can do that by using QTimer::singleShot timers posting signals to the same thread.

See http://qt-project.org/doc/qt-4.8/threads-qobject.html#signals-and-slots-across-threads

PS. If you pass the pointers via queued connections, the pointer must be valid until the signal is processed, which may be after your function which posted the signal existed. A common error is to post signals with strings as a parameters which are stored in a local char[] buffer. At the moment the buffer is accessed the original function is finished, and the string is already gone. Those errors depend on thread scheduling and therefore hard to debug. If you pass the pointers via queued connection, they must be heap-allocated and the callee must be responsible to free them.

Can you change the queue size of a Qt connection?

With a queued connection, for each slot connected to a signal there is a QMetaCallEvent posted to the connected slot object's event queue. The events are delivered when the event loop runs. The code below outputs:

about to emit
done emitting
in aSlot()
class MyObject {
Q_OBJECT
Q_SIGNAL void aSignal();
Q_SLOT void aSlot() { qDebug() << "in aSlot()"; }
public:
MyObject(Qt::ConnectionType conn = Qt::AutoConnection) {
// QObject::connect() defaults the connection type to Qt::AutoConnection,
// we merely duplicate this behavior.
connect(this, SIGNAL(aSignal()), SLOT(aSlot()), conn);
qDebug() << "about to emit";
emit aSignal();
qDebug() << "done emitting";
}
};
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
MyObject obj(Qt::QueuedConnection);
QCoreApplication::processEvents();
return 0;
}

The problem can now be reformulated to: How to force removal of duplicate QMetaCallEvent events from the event queue? This is known as event compression. I have already provided a canonical answer to that question. For user input, you want the most recently emitted signal to be retained, not the oldest one, but I've implemented both behaviors in the answer code.

Using the code from my answer, your example merely needs the following in the main() function:

int main(int argc, char ** argv) {
CompressorApplication<QApplication> app(argc, argv);
app.addCompressedSignal(MyObjType2::staticMetaObject.method(MyObjType2::staticMetaObject.indexOfSignal("obj2Signal(int)")));
MainWindow w;
w.show();
return app.exec();
}

Note: If one were connecting objects with the default Qt::AutoConnection and the objects were in the same thread, then the concept of a queue wouldn't apply at all. The slot is called before the signal function returns and nothing needs to be queued! The code below will output:

about to emit
in aSlot()
done emitting
// MyObject as above
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
MyObject obj;
return 0;
}

Direct connection and event loop

When your signal and slot are in the same thread (as mentioned by user3183610) your signal will be direct connection (the default for same-thread). This effectively runs similarly to a function call. The signal is not queued, but instead the slot that the signal is connected to executes immediately - so you can think of it as a direct function call to the slot.

You can, however, change this behavior by using the Qt::QueuedConnection optional parameter at the end of your connect call:

connect(class_A_object,SIGNAL(signal_A()),class_B_object,on_signal_received, Qt::QueuedConnection);

This will force the use of the queue, your signal will be queued in the event loop and then other pending signals will be executed in order (this is often more desirable then DirectConnection because you can more easily guarantee the order of events). I tend towards to use of queued connections for this reason (though I believe direct is very slightly more efficient).

So for your code there is no return to the event loop until after on_button_click(). During on_button_click() you emit the diret signal signal_x() and immediately on_signal_received() is called (by-passing the event loop), when this finishes it returns back to on_button_click() - just like a function call would :)



Related Topics



Leave a reply



Submit