How to postpone function call as event in the loop?
I have an UDPListener object (derived from QObject) that is run on a dedicated background QThread. It joins a multicast group and keeps reading and propagating datagrams.
UDPListener::run slot (invoked in respons to QThread::started signal) connects to QUdpSocket::readyRead signal (a function "processDatagrams" which keeps reading datagrams as long as there are any and the returns). Then UDPListener reads all already pending datagrams (since readyRead will not be emitted if there are already pending datagrams) with the very same "processDatagrams" and in the end returns. Now the thread runs its loop and whenever readyRead is emitted UDPListener will again read all pending datagrams and return.
This works fine (at least it seems so).
But if I'm not mistaken (correct me if I'm!) if datagrams keep coming fast enough I will keep reading them in the "processDatagrams" function and never return control to the events loop. It might be better to read one datagram at a time and return to events loop. But how would next datagram be read if readyRead will not be emitted in such cases?
So I thought that after reading a datagram I could check if there are any other datagrams and if so then somehow post to the events loop a second invocation of the read method and just return. If there would be no more datagrams then I could just return since if new datagram appears readyRead will be emitted.
Is it proper approach? How to post (postpone in fact) such method invocation?
Create a slot or mark your method mit Q_INVOKABLE and use QMetaObject::invokeMethod:
QMetaObject::invokeMethod(obj, "processDatagrams", Qt::QueuedConnection);
This a method invocation event that is process through the event loop.
[quote author="Adam Badura" date="1364377694"]Then UDPListener reads all already pending datagrams (since readyRead will not be emitted if there are already pending datagrams) with the very same "processDatagrams" and in the end returns. [/quote]
Reading "readyRead":http://qt-project.org/doc/qt-5.0/qtcore/qiodevice.html#readyRead docs you see that is emitted each time a new datagram comes
How doing slot alone will help? Or do you mean that a slot by itself (without adding Q_INVOKABLE) allows incokeMethod?
As to the invokeMethod is it the only way? Looks bad for me. Passing a string is problematic since it requires me to update it if I change the name since compiler will not see error there. Also it suggests that there will be a string-based look-up for the proper entry.
Is there any other way or is it the only one?
[quote author="mcosta" date="1364378405"]
Reading "readyRead":http://qt-project.org/doc/qt-5.0/qtcore/qiodevice.html#readyRead docs you see that is emitted each time a new datagram comes [/quote]
"QUdpSocket":http://qt-project.org/doc/qt-5.0/qtnetwork/qudpsocket.html documentation says differently:
bq. Note: An incoming datagram should be read when you receive the readyRead() signal, otherwise this signal will not be emitted for the next datagram.
A slot is invokable, but not the other way around. You can also just use a signal/slot connection that you also connect as queued. Or, you push your data into a queue, and use a timer to process that queue.
If you aren't sure your read rate isn't enought fast you could limit the number of datagrams to deque in single slot and use a timer and periodically check if there are pending datagrams to process.
However, if the producer (process that sends datagrams) is always faster than consumer (your process), you have a problem and you should consider to change your architecture.
I just tried a version in which:
UDPListener in its "run" method (slot for QThread::started signal) connects "processDatagram" slot to QUdpSocket::readyRead and the calls "processDatagram" explicitly if there is any datagram pending (QUdpSocket::hasPendingDatagrams).
"processDatagram" reads and processes the datagram (by emitting another signal) and then if there is any pending datagram it calls the QMetaObject::invokeMethod for itself (as described above).
So essentially where I previously had a while loop (on QUdpSocket::hasPendingDatagrams) that called "processDatagram" no I have an if (on QUdpSocket::hasPendingDatagrams as well) that calls "processDatagram" by invokeMethod.
But when I run the application it was nearly non-responsive. I'm not sure why. I checked few times by interrupting and most commonly it stopped in the worker thread processEvent (some Qt internal function). Sometimes in main thread in the slot called from the thread.
I have to investigate it more.
are you sure you can't manage receiving only with readyRead signal and looping in slot?
Have you make some tests about your receiving rate?
You can try with
in "processDatagram" fix the maximum number of datagram to read
use a Timer and connect "timeout()" signal with "processDatagram"
In this way processDatagram is called upon receiving readyRead signal and periodically
If you can, share the code with us, so we might be able to see more clearly what is happening.
I posted the code in "a different thread":http://qt-project.org/forums/viewthread/26145/#119544 (a different issue with same code). Please have a look there.
Firstly I shall note that I don't know if that is needed at all. I guess it is not. At least initial tests show that the loop-based code is just fine. But since I'm also learning Qt and exploring it I considered this issue and means to work around it.
So what I don't like in that code is the UDPListener::processDatagrams function. It contains a loop that could (theoretically) "hang" the thread in which UDPListener runs (NetworkWatcher::theUDPListenerThreadPtr) by never returning to processing events. This could be also an issue when you try to quit the thread.
So I changed (along your suggestions) the code by replacing the loop with a condition, so now it is:
if ( theSocketPtr->hasPendingDatagrams() )
QMetaObject::invokeMethod( this, "processDatagram", Qt::QueuedConnection );
(I do know it could be simplified a bit but I don't think that is matters here much.)
The observation is that after that change the application (main thread!) drops in responsiveness dramatically. Why is that?