Table of Content
Written By : Girish Ramakrishnan, ForwardBias Technologies
The timer APIs
Qt provides two APIs to work with timer.
- QObject::startTimer [doc.qt.nokia.com] – Creates a recurring timer for use by any QObject subclass and returns the timer id. When the timer expires it receives a QEvent::Timer that can be handled by overriding QObject::timerEvent(QTimerEvent *). The QTimerEvent argument contains the timer id which can be matched against if the QObject uses multiple timers. QObject::killTimer(id) can be used to stop and disarm the timer.
- QTimer [doc.qt.nokia.com] – QTimer is a QObject which emits the elapsed() signal when the timer expires. It simply uses QObject::startTimer() and in the QObject::timerEvent() event handler, it emits the elapsed() signal.
QtEventProcessing [developer.qt.nokia.com] provides an overview of how platform events are processed by Qt.
QAbstractEventDispatcher [doc.qt.nokia.com] is the interface for the event/message pump. An event dispatcher exists for each thread created using Qt. It requires registerTimer [doc.qt.nokia.com] and unregisterTimer [doc.qt.nokia.com] to be implemented for timers.
There are two versions of the registerTimer (one virtual and one non-virtual). Both versions take an argument to QObject that the timer is to be associated with. The non-virtual registerTimer (cross-platform) allocates a unique timer id and calls the virtual registerTimer for actually registering/creating the timer in a platform-specific way. With this approach, the platform event dispatcher maintains the list of timers associated with a QObject. QAbstractEventDispatcher::registeredTimers [doc.qt.nokia.com] can be used to query the list of timers associated with a QObject.
At the low level, once you register a timer, its interval cannot be changed and should be considered as immutable. A high-level API has to create a new timer to change the interval. Also, timers keep firing (i.e recurring) until killed and there is no concept of a “single shot” in the event dispatcher interface. High level APIs have to emulate a single shot timer by unregistering the timer after the first interval expires.
Timer id allocation algorithm
The algorithm for timer id allocation is lock-free and can be studied from Brad’s blog [labs.trolltech.com].
An important fact is that timer ids are required to be unique even across threads. This is because when a QObject moves from one thread to another, its timers also move along with it (i.e signals and events are now delivered through the new thread’s event loop). Moving the timer is simply a matter of unregistering the timer from the old thread’s dispatcher and registering in the new thread’s dispatcher. If the timer ids were not unique, the timer ids might clash with existing timers in the new thread. If fresh timer ids were to be regenerated on a QObject::moveToThread, then the application needs to be notified about the id changes and this is a hassle for the application programmer.
Unix (without glib)
On Unix, QEventDispatcherUNIX implements the event dispatcher. It maintains a list of timers sorted by their expiry time. When a timer is registered, it’s expiry time is computed by adding the timeout value to monotonic clock (clock_gettime). On systems that do not support monotonic clock, gettimeofday is used. The timer is then inserted into a list sorted based on its expiry interval (first item expires first).
The dispatcher relies on select() to wait for events on all fds (the X connection, sockets). It first updates the expiry interval for all the registered timers when processEvents() is entered. It then provides the timeout to the select() system call as the interval in the first item of the timer list. After returning from select, the dispatcher “activates” expired timers by sending the associated QObjects a QEvent::Timer.
Note that Qt does not use the POSIX timer API – timerfd_create (Why? Portability?).
Unix (with glib)
Qt can be compiled to use the glib event loop (the default) which helps it integrate better with Gtk. Glib loop documentation [developer.symbian.org]” provides an introduction to how glib loops are written.
Just like QEventDispatcherUNIX, QEventDispatcherGlib manages timers in a list sorted on expiry time. It creates a new GSource for timers. The prepare, check and dispatch functions of this custom GSource work on the timer list in the obvious way.
As noted in QtEventProcessing [developer.qt.nokia.com], Qt creates a hidden window with a callback function to process events. When a timer is registered, QEventDispatcherWin creates a native Windows timer based on the timer interval.
- If the interval is greater than 20 msecs, it uses SetTimer (with the id that was generated by QAbstractEventDispatcher::registerTimer). SetTimer sends a WM_TIMER message to the callback function that gets sent as QEvent::Timer to the QObject.
- If the interval is less than 20 msecs, Qt tries to use multimedia (aka fast) timers using timeSetEvent. timeSetEvent takes a callback function that is called on expiry and will be called from a separate thread. In Qt, timers fire in the same thread as that of the QObject. So the timeSetEvent’s callback function posts a message to the dispatcher which the dispatcher picks up and sends as QEvent::Timer.
- If the interval is 0, the dispatcher posts a special message to itself (QEvent::ZeroTimerEvent) on registration. The dispatcher processes this event by sending a QEvent::Timer to the corresponding QObject and posts a ZeroTimerEvent to itself.
QObject::startTimer registers a new timer with event dispatcher. This is roughly the equivalent of, QAbstractEventDispatcher::instance(object->thread())->registerTimer(interval, object).
QTimer is a QObject. All it does is call QObject::startTimer(). It emits activated() signal in it’s timerEvent().
QTimer feels heavy since it’s a QObject and in general Qt code tries to avoid it. At the same time, using the QObject::startTimer() API is a bit error-prone.
- When stopping a timer, you have to invalidate the timer id (say to -1) after QObject::killTimer(m_timerId). This is required since a positive value for m_timerId will indicate to your code that the timer is active.
- To restart a timer, one needs to remember to kill the old timer if m_timerId is not -1.
QBasicTimer solves the above and is just a glorified interface to the integer “timer id”. QBasicTimer::start(int msec, QObject *) kills any existing timer and create a new one using object->startTimer(msec). QBasicTimer::stop() will kill the timer and invalidate the id. QBasicTimer::timerId() provides the timer id.
- A never-ending struggle [labs.trolltech.com] – Timer id allocation algorithm