3

I am using Qt5 on Windows7 platform.
In my current app I need a timer to fire every minute ("per minute"), from minute 00 to 59...
I have experimented various ideas, but my (previous) solutions had some issues like: misfire (no timeout triggered for a certain minute) or double-fire (timeout triggered twice for the same minute!).
Finally, I currently reached to this implementation:

static QTimer timer;

static int GetInterval()
{
    QDateTime now(QDateTime::currentDateTime());
    return ((60 - now.time().second()) * 1000 - now.time().msec());
}

void TEST_TIMER(void)
{
    QObject::connect(&timer, &QTimer::timeout, []()
    {
    qDebug() << " Triggered! " << QDateTime::currentDateTime().time().minute()
                               << QDateTime::currentDateTime().time().second()
                               << QDateTime::currentDateTime().time().msec();
        timer.start(GetInterval());
    } );
    timer.start(GetInterval());
}    

And here is the output:

 Triggered!  34 59 550
 Triggered!  35 0 3
 Triggered!  36 0 15
 Triggered!  37 0 28
 Triggered!  38 0 41
 Triggered!  39 0 54
 Triggered!  40 0 68
 Triggered!  41 0 82
 Triggered!  42 0 97
 Triggered!  43 0 109
 Triggered!  44 0 123
 Triggered!  45 0 137
 Triggered!  46 0 149
 Triggered!  47 0 165
 Triggered!  48 0 178
 Triggered!  49 0 192
 Triggered!  50 0 205
 Triggered!  51 0 217
 Triggered!  52 0 231
 Triggered!  53 0 244
     ...   

Seems ok, except the first line: Triggered! 34 59 550 :( Why?
Also, why is there that up-drift of about 12-13 msecs/minute?.
So, not being expert in this matter I prefer to ask:
Is this implementation ok? Can it be improved to avoid unpleasant situations like double-fire and/or misfire?

סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68

2 Answers2

2

From QTimer description (Qt::CoarseTimer being the default):

For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QTimer may wake up earlier than expected, within the margins for those types: 5% of the interval for Qt::CoarseTimer and 500 ms for Qt::VeryCoarseTimer.

So with a 5% accurary, your first shot can be much earlier than expected, and that explains:

Triggered!  34 59 550
Triggered!  35 0 3

If the timer shots (just) before the 0 minute, it will shoot again to align to the minute that's not yet reached, even if it's a few milliseconds away.

If you use a Qt::PreciseTimer instead, it will never time out earlier than expected, so you won't have this problem (pad the delay with a few ms to be sure).

The Qt::CoarseTimer also probably explains the small drift you're seeing, as nothing states that the error margin is random.

Ilya
  • 5,377
  • 2
  • 18
  • 33
  • I (totally) agree that using `Qt::PreciseTimer` works, as it really doesn't time out earlier. Yet, I am still puzzled **WHY** do I have that _quasi-constant_ 12msec/minute drift if I use `Qt::CoarseTimer`? Should I be worried about a misfire later on (after 5000 minutes), if I am using the default timer type `Qt::CoarseTimer`? – סטנלי גרונן Jan 19 '16 at 11:14
  • 1
    I think that the drift you see (always upward) is an accident, in the general case it's random. It will stop dirfting at some point. However, if you're using CoarseTimer with your current code, you'll always risk a double-fire, not because of that, but because of the early timeout. – Ilya Jan 19 '16 at 11:18
  • Yup, I suppose that makes sense!... So, the best I can do is to use `Qt::PreciseTimer` and that should fix all the issue :) – סטנלי גרונן Jan 19 '16 at 11:22
  • Yes. In the case it's not accurate enough (it depends on the OS), you can "protect" the output by checking the interval, something like `if( interval < 100 ) interval = 60*1000`; – Ilya Jan 19 '16 at 11:29
  • BTW: "Nothing is _an accident_, everything happens with a reason !" :) – סטנלי גרונן Jan 19 '16 at 11:31
  • Well, that's an interesting philosophical question that I can't answer. But did I answer your other questions ..? – Ilya Jan 19 '16 at 11:34
  • I stand corrected, there's an answer to that too: http://stackoverflow.com/a/3963140/5695504 – Ilya Jan 19 '16 at 12:11
1

I need a timer to fire every minute

Why not keep things simple?

QTimer* pTimer = new QTimer;
connect(pTimer, &QTimer::timeout, [=](){
    // do something
};

// fire every 60 seconds
// 1 * 1000 is every second
pTimer->Start(1 * 1000 * 60) 

Note that a timer will keep firing, unless you set setSingleShot(true), stop the timer, or delete it.

TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • I remember I did try something like that and I noticed the timer "misfired" sometimes, i.e. I noticed in the logs something like: Triggered min.04, Triggered min.05, Triggered min.07, Triggered min.08, etc. So, somehow the `Triggered min.06` was MISSING! I don't know why, probably timer drift, message loop? That is why I complicated things like my code above... BTW: I also noticed timer _double-fired_ from time to time... :( – סטנלי גרונן Jan 19 '16 at 09:30
  • 1
    Have you read about the [accuracy of QTimers?](http://doc.qt.io/qt-5/qtimer.html#accuracy-and-timer-resolution). As for double-firing, that will be due to the way in which you setup the connections to the timer. – TheDarkKnight Jan 19 '16 at 09:40
  • Yes, I know the default value is `Qt::CoarseTimer`. On Windows, normal Windows timers are used for Qt::CoarseTimer. That still doesn't explain the constant drift of 12msec/minute... I guess there's something wrong in my code... – סטנלי גרונן Jan 19 '16 at 10:06
  • You said "that will be due to the way in which you setup the connections to the timer." Please, can you elaborate a little bit? Thx in advance. – סטנלי גרונן Jan 19 '16 at 16:47
  • I'd need to see all the code, in context, but the usual cause for multiple firing of slots is due to multiple connection signals. Note that you can protect against this by setting the connectType to [Qt::UniqueConnection](http://doc.qt.io/qt-5/qt.html#ConnectionType-enum), but ideally, you'd know when you were making those connections and not need this. – TheDarkKnight Jan 19 '16 at 17:21
  • Yes, I've read about `Qt::UniqueConnection`. I guess such a _protection_ won't harm, yet I do have the feeling that the problem was related just to the fact that all QTimers except `Qt::PreciseTimer` are subject to fire before or after the expected set timeout within a very large range +/-500msec. So, back on subject: I guess leaving the connection type as `Qt::AutoConnection` would be just fine...? – סטנלי גרונן Jan 19 '16 at 17:49