X
11083 Rate this article:
No rating

Coming in IDL 8.6 - Repeating Timers

Jim Pendleton

A couple years ago I wrote a blog article on the topic of the new TIMER class in IDL 8.3. In the upcoming IDL 8.6 release, a new REPEAT keyword will be added to the Timer::Set method.

We like to write device control software in the Custom Solutions Group here at NV5 so this new feature will come in handy.

The typical case for using a timer with REPEAT arises from the need to ensure processing at a regular interval, for example in the display of an animation at a specific frames-per-second rate, or in the consistent collection of data from a device.

This REPEAT keyword is a toggle to force the timer to fire at the specified interval. This relieves the callback code of the need for an explicit call to manually reset the timer during the processing of the preceding event. It produces a truly regular "heartbeat" of events.

It is most appropriate to use this style of timer when the processing time of the callback is less than the timer's interval. If the timer events cannot be drained at a rate greater than the interval at which they're added to the queue, this solution is not generally recommended, or the interval needs to be adjusted accordingly.

The IDL Code Profiler should be used to determine if there is a potential for conflict between the callback execution time and the sampling rate. Also be aware that your measurements will depend on the current platform and system load, so be judicious when deploying solutions like this to other computers with different performance characteristics from the device under test.

A timer callback can be thought of as a higher-priority interrupt to the main IDL interpreter loop. It will take precedence over the otherwise-executing code.

When the interpreter is between execution of lines of .pro code, if a timer event is found on the queue the timer's callback routine will be executed first, taking precedence over the next line of IDL code. This also applies to compiled code in IDL SAVE files.

When the callback is completed, the interpreter returns to its regularly-scheduled execution, which may include the processing of additional timer callbacks.

In other words, the IDL interpreter remains single-threaded. The timer callback will be executed as an interruption to the currently-executing context. In fact, a timer will interrupt even modal WIDGET_BASE dialogs. In many cases, it may be wise to use a mechanism such as TIMER's ::Block/::Unblock methods or a system semaphore to prevent the possibility of re-entrance.

Example

The following source code shows the behavior of the timer event queue when /REPEAT is set.

PRO myTimerCallback3, id, c
COMPILE_OPT IDL2
t = systime(1)
c.dt.add, t - c.t
c.t = t
c.counter++
wait, c.counter ge 20 && c.counter le 40 ? .51 : .001
print, 'in callback ', c.counter
done = c.counter eq 100
if (done) then begin
    p  = plot(c.dt.toarray(), ytitle = 'dt', xtitle = 'iteration', $
        title = 'Execution time per iteration')
    void = Timer.Cancel(id)
endif
END


PRO async_timer_example_repeat3
COMPILE_OPT IDL2
; Create a timer that will fire 5 times per second.
c = {counter : 0L, t : systime(1), dt : list()} 
id = Timer.Set( 0.2, 'myTimerCallback3', c, /REPEAT )
END

A new timer object is created that fires off events at an interval of 0.2 seconds, regardless of what else may be going on in the IDL main thread.

In the callback, the first 19 iterations are executed with just a 1-millisecond WAIT delay, simulating a light CPU load.

Iterations 20 through 40 are executed with a 0.51-second delay, over twice the timer object's repetition interval.  This simulates a heavier CPU load and helps to show that the timer continues to run and queue events at its own rate in the background.

The remaining steps revert to the shorter wait time.

Internally, the code keeps track of the clock time between callback execution at each step.

The plot of the result is shown below.

The labels indicate the dominant driver for the execution rate at each interval.

Initially, the timer's heartbeat keeps us on track with a regular 0.2 second interval between callbacks.

The next set of iterations are dominated by the 0.51 second WAIT inside each callback.  If we had been displaying a video playback, the frame rate would be decreased substantially during this time.

The third set of iterations shows the effect of emptying the queue of the accumulated timer events that could not be processed immediately at the time they were fired, due to the WAIT statements in the previous block. If this had been video playback, these frames would have all been displayed very rapidly.

After the queue is emptied, the regular heartbeat is attained again. Video playback would return to "real time".

If you were to remove the /REPEAT keyword and add a call to Timer::Set in the callback routine, you could compare the effects. Your choice in your own code will be based on your prioritization of "real time" versus "lagging but consistent" sampling.