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).
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.
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.
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.
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.
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:
You can also setup some logic here to detect skipped intervals (say one of the operations takes 11 seconds to complete...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With