Timer callbacks

The template class generic_timer_service defines the interface which receivers of timer callbacks can use to register themselves. The single template parameter determines the data type used to represent time values.

A specialization of this template, timer_service, uses boost::posix_time::ptime to represent time values, and is thus suitable to cooperate with boost::posix_time::microsec_clock and ioready_dispatcher to dispatch wall clock timer events.

"Registration for events"

Interested receivers can register functions to be called at specified points in time at the generic_timer_service interface. Receivers can use the generic_timer_service::timer functions for this purpose; they can be used in the following fashion:

                class TimerHandler {
                public:
                        bool onTimer(long time) throw()
                        {
                                // perform timer action
                                time+=1000;
                                return true; // rearm timer 1000 units in the future
                        }
                        void finish(void) throw()
                        {
                                delete this;
                        }
                        void register(tscb::generic_timer_service<long> *service) throw(std::bad_alloc)
                        {
                                // call us at point "6000" in time
                                service->timer(boost::bind(&TimerHandler::onTimer, this, _1),
                                        6000);
                        }
                }

In the previous example, the onTimer method of the corresponding object will be called at the specified point in time; moreover the timer will rearm itself 1000 "units" in the future by returning true, effectively creating an (imprecise) periodic timer (see section Precise periodic timers below how to create precise periodic timers). Returning false from the timer callback functions indicates that it does not want to be called again.

The callback function is passed the current time (as it is known to the callback service provider) as first argument. It is very likely that this is slightly after the desired point in time (due to notification and scheduling latency); the implementation can find out by what amount of time the callback is to late by inspecting this value and comparing with the desired point in time.

Callback link handles for timer callbacks

The generic_timer_service::timer functions returns a reference to a callback link object that represents the connection between the callback service provider and the receiver. The return value can be stored by the caller:

                tscb::timer_callback link;
                link=service->timer<TimerHandler, &TimerHandler::onTimer, &IOHandler::finish>
                        (tscb::current_time()+1000, this);

The link object can later be used to cancel the timer:

                link->disconect();

Afterwards, the timer function will not be called again. If the timer callback function is running concurrently in another thread and it returns True (indicating that it would like to rearm itself) it is cancelled nevertheless. Calling disconnect always takes precedence.

Precise periodic timers

Since timer callback functions may be called with a slight "lag", it is not possible to implement precise periodic timers by simply incrementing the current time with a fixed value to calculate the next callback time. Instead the receiver has to keep track of the "originally desired" point in time and used that as a base instead:

                class TimerHandler {
                public:
                        long long next_callback_due;
                        
                        bool onTimer(long long time) throw()
                        {
                                // calculate amount of time we are late
                                long long lag=time-next_callback_due;
                                // perform timer action
                                
                                next_callback_due+=1000;
                                time=next_callback_due;
                                return true; // rearm timer 1000 usecs in the future
                        }
                        void finish(void) throw()
                        {
                                delete this;
                        }
                        void register(tscb::timer_service *service) throw(std::bad_alloc)
                        {
                                next_callback_due=current_time()+1000;
                                
                                // call us 1000 usecs in the future
                                service->timer<TimerHandler, &TimerHandler::onTimer, &TimerHandler::finish>
                                        (tscb::next_callback_due, this);
                        }
                }

Programmers are encouraged to use this feature to provide if they want to be called again (either periodically, or with varying timeouts); it is more efficient to rearm an existing timer with a new timeout value than to unregister an expired timer and create a new one.

Timer dispatchers

Free-standing implementations of the timer_service interface suitable for timer event dispatching are provided by the generic_timerqueue_dispatcher class template (and its specialization, timerqueue_dispatcher, respectively). The implementations use a fibonacci heap (see Fibonacci Heap) to store pending timers.

The generic_timerqueue_dispatcher provides the run_queue member function; it expects the current time value to be given and will process all timers that have expired up to this point in time. If timers are added/modified during this queue run so that their expiry time is equal to, or before the current time, they will be processed as well.

The function returns true if there are timers pending after processing the queue, and will indicate the point in time when the next callback is due. It should be called in the following fashion:

                tscb::timerqueue_dispatcher *dispatcher;
                ...
                long long next_timer;
                long long now=tscb::current_time();
                bool timer_pending;
                do {
                        next_timer=now;
                        // process timers
                        timer_pending=dispatcher->run_queue(next_timer);
                        // no more timers pending? -> abort
                        if (!timer_pending) break;
                        // re-read current time
                        now=tscb::current_time();
                        // compare with next pending timer and run again, if required
                } while(now>=next_timer);
                
                // wait appropriate amount of time, until next timer is due
                if (timer_pending)
                        sleep_milliseconds(next_timer-now);

As shown above, the current time should be rechecked after processing the timer queue because processing the timer callbacks takes its time as well.

The returned time value can be used to calculate the time to "sleep" until the next timer is due. Since timers can be added during the "sleep" period, the dispatcher cooperates with an eventflag to interrupt the sleep if a timer has been added that would expire within the sleep period:

                tscb::eventflag *flag;
                tscb::timerqueue_dispatcher *dispatcher;
                ...
                dispatcher=new tscb::timerqueue_dispatcher(flag);

The caller must atomically wait on the eventflag and the timeout value calculated above. One way to achieve this is to couple the timer dispatching with an ioready_dispatcher; see Compound event dispatching how to simultaneously dispatch timer and io events from one thread.


Generated on Tue Jan 12 21:30:49 2010 for libtscb by  doxygen 1.5.6