سؤال

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 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...

نصائح أخرى

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.

The answer is yes. When your QTimer and your receiver are in different threads, the call is put in the receivers event queue. And if your picture taking or saving routine is hogging execution time, your event can be delayed tremendously. But this is the same for all events. If a routine does not give control back to the event loop, your gui hangs. You can use:

Qt::BlockingQueuedConnection Same as QueuedConnection, except the current thread blocks until the slot returns. This connection type should only be used where the emitter and receiver are in different threads.

But most likely a situation like this is a hint that something is wrong with your logic.

You can use the Qt::(Blocking)QueuedConnection connection type for the connect method to avoid direct connections that shot immediately.

Since you have separate threads, you should use the blocking version. However, you should consider the non-blocking variant when you wish to avoid direct calls without separate thread for the receiver.

Please see the official documentation for details.

From the documentation for your convenience:

Qt::QueuedConnection

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.

Qt::BlockingQueuedConnection

Same as QueuedConnection, except the current thread blocks until the slot returns. This connection type should only be used where the emitter and receiver are in different threads.

What you probably meant to write is that you would not like to have direct connection rather than queued.

QCoreApplication::removePostedEvents ( QObject * receiver, int eventType ) with the event type MetaCall can be used or cleaning up the queue if it is getting saturated with those heavy tasks. Also, you could always use a flag for communicating this with the slot to exit if that is set.

See the following forum discussion for details, as well: http://qt-project.org/forums/viewthread/11391

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top