Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens with Qt signals when the receiver is busy?

In my application, I have an instance of QTimer, whose timeout() signal is connected to a slot in the main window object, causing it to get called periodically. The slot takes a picture with a camera and saves it to disk.

I was wondering what happens if the signal is emitted (from a separate thread where QTimer executes, I presume) when the receiver (the window object on the main thread) is currently busy (like with taking and saving the previous picture). Does the call get queued and executed after the previous call terminates? The whole idea is to have it run at regular intervals, but can those calls queue up and then get called randomly when control returns to the event loop, causing a mess? How can I avoid it? Theoretically the slot should execute quickly, but let's say the hardware had some problem and there was a stall.

I would like for calls to be dropped rather than queued in such a situation, and even more useful would be the ability to react when it happens (warn user, terminate execution).

like image 546
neuviemeporte Avatar asked Sep 07 '13 21:09

neuviemeporte


People also ask

How does a QTimer object help in managing time intervals?

The QTimer class provides a high-level programming interface for timers. To use it, create a QTimer, connect its timeout() signal to the appropriate slots, and call start(). From then on, it will emit the timeout() signal at constant intervals.

How do signals work in Qt?

Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object's client or owner. Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.

How do you wait for a signal in Qt?

You can use a local event loop to wait for the signal to be emitted : QTimer timer; timer. setSingleShot(true); QEventLoop loop; connect( sslSocket, &QSslSocket::encrypted, &loop, &QEventLoop::quit ); connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit ); timer.

Can a QT signal return a value?

All signals and slots have a "void" return value. You cannot return anything that way. This code assumes that there is only one slot that will be connected to the signal.


2 Answers

The other answers at this moment have relevant context. But the key thing to know is that if the timer callback is signaling a slot in a different thread, then that connection is either a QueuedConnection or a BlockingQueuedConnection.

So, if you're using the timer to try and get some sort of regular processing done, then this gives you some additional jitter in timing between when the timer fires and when the slot actually executes, as the receiving object is in it's own thread running an independent event loop. That means it could be doing any number of other tasks when the event is put in the queue and until it finishes processing those events, the picture thread won't execute your timer event.

The timer should be in the same thread as the photo logic. Putting the timer in the same thread as the camera shot, makes the connection direct, and gives you better stability on your timing intervals. Especially if the photo capture & save has occasional exceptional durations.

It goes something like this, supposing the interval is 10 seconds:

  • set timer for 10 seconds
  • timer fires
  • save a start time
  • take photo
  • save photo to disk (say it takes 3 seconds for some odd reason)
  • calculate 10-(current time - start time)= seven seconds
  • set time out for seven seconds

You can also setup some logic here to detect skipped intervals (say one of the operations takes 11 seconds to complete...

like image 120
Digikata Avatar answered Oct 21 '22 18:10

Digikata


I detail here after some experimentation how QTimer behaves when the receiver is busy.

Here is the experimentation source code: (add QT += testlib to the project file)

#include <QtGui>
#include <QtDebug>
#include <QTest>

struct MyWidget: public QWidget
{
    QList<int> n;    // n[i] controls how much time the i-th execution takes
    QElapsedTimer t; // measure how much time has past since we launch the app

    MyWidget()
    {
        // The normal execution time is 200ms
        for(int k=0; k<100; k++) n << 200; 

        // Manually add stalls to see how it behaves
        n[2] = 900; // stall less than the timer interval

        // Start the elapsed timer and set a 1-sec timer
        t.start();
        startTimer(1000); // set a 1-sec timer
    } 

    void timerEvent(QTimerEvent *)
    {
        static int i = 0; i++;

        qDebug() << "entering:" << t.elapsed();
        qDebug() << "sleeping:" << n[i]; QTest::qSleep(n[i]);
        qDebug() << "leaving: " << t.elapsed() << "\n";
    }   
};  

int main(int argc, char ** argv)
{
    QApplication app(argc, argv);   
    MyWidget w;
    w.show();
    return app.exec();
}

When the execution time is smaller than the time interval

Then as expected, the timer runs steadily shooting every seconds. It does take into account how much time the execution has taken, and then the method timerEvent always starts at a multiple of 1000ms:

entering: 1000 
sleeping: 200 
leaving:  1201 

entering: 2000 
sleeping: 900 
leaving:  2901 

entering: 3000 
sleeping: 200 
leaving:  3201 

entering: 4000 
sleeping: 200 
leaving:  4201 

When only one click is missed because the receiver was busy

n[2] = 1500; // small stall (longer than 1sec, but less than 2sec)

Then, the next slot is called right away after the stall is finished, but the subsequent calls are still multiple of 1000ms:

entering: 1000 
sleeping: 200 
leaving:  1200 

entering: 2000 
sleeping: 1500 
leaving:  3500 // one timer click is missed (3500 > 3000)

entering: 3500 // hence, the following execution happens right away
sleeping: 200 
leaving:  3700 // no timer click is missed (3700 < 4000)

entering: 4000 // normal execution times can resume
sleeping: 200 
leaving:  4200 

entering: 5000 
sleeping: 200 
leaving:  5200 

It also works if the following clicks are also missed due to the accumulation of time, as long as there is only one click that is missed at each execution:

n[2] = 1450; // small stall 
n[3] = 1450; // small stall 

output:

entering: 1000 
sleeping: 200 
leaving:  1201 

entering: 2000 
sleeping: 1450 
leaving:  3451 // one timer click is missed (3451 > 3000)

entering: 3451 // hence, the following execution happens right away
sleeping: 1450 
leaving:  4901 // one timer click is missed (4901 > 4000)

entering: 4902 // hence, the following execution happens right away
sleeping: 200 
leaving:  5101 // one timer click is missed (5101 > 5000)

entering: 5101 // hence, the following execution happens right away
sleeping: 200 
leaving:  5302 // no timer click is missed (5302 < 6000)

entering: 6000 // normal execution times can resume
sleeping: 200 
leaving:  6201 

entering: 7000 
sleeping: 200 
leaving:  7201 

When more than one click is missed because the receiver was very busy

n[2] = 2500; // big stall (more than 2sec)

If two or more clicks are missed, only then a problem appear. The execution times are not synchronized with the first execution, but rather with the moment the stall finished:

entering: 1000 
sleeping: 200 
leaving:  1200 

entering: 2000 
sleeping: 2500 
leaving:  4500 // two timer clicks are missed (3000 and 4000)

entering: 4500 // hence, the following execution happens right away
sleeping: 200 
leaving:  4701 

entering: 5500 // and further execution are also affected...
sleeping: 200 
leaving:  5702 

entering: 6501 
sleeping: 200 
leaving:  6702 

Conclusion

The solution of Digikata has to be used if the stalls might be longer than twice the timer interval, but otherwise it is not needed, and the trivial implementation as above works well. In the case you'd rather have the following behaviour:

entering: 1000 
sleeping: 200 
leaving:  1200 

entering: 2000 
sleeping: 1500 
leaving:  3500 // one timer click is missed 

entering: 4000 // I don't want t execute the 3th execution
sleeping: 200 
leaving:  4200 

Then you can still use the trivial implementation, and just check that enteringTime < expectedTime + epsilon. If it is true, take the picture, if it is false, do nothing.

like image 28
Boris Dalstein Avatar answered Oct 21 '22 18:10

Boris Dalstein