Asynchronous timers provide IDL with non-blocking timers. They offer the following advantages over widget-based asynchronous timers:
- Asynchronous timers work in headless environments, independent of widgets
- Higher theoretical resolution (Unix: nanoseconds, Windows: microseconds)
- Optional high-precision repeating timer
Asynchronous timers can fire and have their callback invoked in these situations:
- While IDL is idle.
- While IDL is processing statements in the interpreter.
Asynchronous timers will not fire in these situations:
- While IDL is processing C code, including both built-in routines and DLMs.
- While IDL is processing a call to wait.
- While native dialogs are onscreen [e.g., DIALOG_PICKFILE(), DIALOG_MESSAGE(), etc.], however, they will fire when dialogs built from IDL widgets have popped up.
-
While system callbacks are executing. Some examples of callbacks are: timer callbacks, widget event handlers, object cleanup methods, etc. When a timer fires while a callback is executing, its callback will be invoked when the current one finishes.
- While explicitly blocked by the timer.Block method.
Note: Asynchronous timers in IDL are actually implemented as a collection of static methods. Because of this, there are no "Creation" or "Syntax" sections in this document for creating timers.
Methods and Additional Information
Examples
Example 1 - Callback Specified
PRO myTimerCallback, id, userData
COMPILE_OPT IDL2
PRINT, 'myTimerCallback( ', STRTRIM( id, 2 ), ', ', userData, ' )'
END
PRO async_timer_example_1
COMPILE_OPT IDL2
id = Timer.Set( 3.14, 'myTimerCallback', 'woof' )
END
Example 2 - Object Specified
PRO AsyncTimerExample2::handleTimerEvent, id, userData
COMPILE_OPT IDL2
PRINT, 'AsyncTimerTester::handleTimerEvent( ', STRTRIM( id, 2 ), ', ', userData, ' )'
END
PRO AsyncTimerExample2__define
COMPILE_OPT IDL2
!null = { AsyncTimerExample2, $
INHERITS IDL_Object $
}
END
o = AsyncTimerExample2()
id = Timer.Set( 3.14, o, 'w' + 'o' + 'o' + 'f' )
END
Example 3 - Blocked Timers
PRO handleTimer, id, userData
COMPILE_OPT IDL2
PRINT, '*** Timer Fired ***'
*userData = 0
END
PRO busyWait, t
COMPILE_OPT IDL2
t0 = SYSTIME( /SECONDS )
WHILE ( ( SYSTIME( /SECONDS ) - t0 ) lt t ) DO BEGIN
ENDWHILE
END
PRO async_timer_example_3
COMPILE_OPT IDL2
criticalData = PRT_NEW( 1 )
!NULL = timer.Set( 1.0, 'handleTimer', criticalData )
PRINT, 'Data before safe zone: ', STRTRIM( *criticalData, 2 )
timer.Block
busyWait, 2.0
PRINT, 'Data in safe zone: ', STRTRIM( *criticalData, 2 )
timer.Unblock
PRINT, 'Data after safe zone: ', STRTRIM( *criticalData, 2 )
END
Timer::Block
The Timer::Block method allows you to block an asynchronous timer. If a timer's callback code could potentially interrupt and disrupt other code, the timer should be blocked. An example is when the timer's calleback code could access the same data as the code that it might interrupt. See Example 3.
Code that runs in between the calls to Block and Unblock will not be interrupted by any timer. IDL automatically blocks timer callbacks from interrupting system callbacks, including timer callbacks, widget callbacks, drag and drop callbacks, etc. Explicit blocking is unnecessary in these types of callbacks. Invocations of the Fire method will work regardless of blocking.
The Block and Unblock methods must always be paired. They can be nested. If they are not paired, then timer blocking will not work as expected. To help debug mismatches, the procedures have a COUNT keyword that returns the current nesting level after the call to block or unblock. The returned value typically ranges from 0 to MaxLong, where 0 means blocking will not occur and values greater than 0 indicate the nesting level. Negative values indicate that too many calls to Unblock have been made, and Block and Unblock have not been correctly paired.
Syntax
Timer.Block [, COUNT]
Arguments
None
Keywords
COUNT
Set this keyword to a named variable that will contain a long integer with the nesting level of the Block and Unblock methods.
Timer::Cancel
The Timer::Cancel method cancels a timer.
Note: If you are within the Timer callback when you call Timer.Cancel, your UserData variable will become undefined. To avoid this behavior you should make a copy of your UserData variable before calling the Cancel method.
Syntax
Result = Timer.Cancel( ID )
Result = Timer.Cancel( ALL=value )
Return Value
Returns a 0 or 1 to indicate the success or failure of the call. A return value of 1 indicates the call was successful, while a 0 indicates that the identifier (id) was invalid and the call was not successful.
Arguments
ID
A long integer that is the timer's identifier. This is the same value as returned by the "set" function.
Keywords
ALL
Use in place of ID to indicate whether all timers should be canceled. Valid values are Boolean (1 for yes or 0 for no).
Note: If you specify /ALL (or ALL = 1) in conjunction with the ID argument, the /ALL takes precedence and IDL ignores the ID.
Timer::Fire
You can launch a timer immediately, before it expires, by using the Timer.Fire method. If you originally supply user data, it is replaced with the new user data.
Note: If you use Timer::Fire, the timer will be "complete" and will not go off again.
Syntax
Result = Timer.Fire( ID [, UserData ] )
Return Value
Returns a 0 or 1 to indicate the success or failure of the call. A return value of 1 indicates the call was successful, while a 0 indicates that the identifier (ID) was invalid and the call was not successful.
Arguments
ID
The timer's identifier (a long integer). This is the same value as returned by the "set" function.
UserData
Optional data to supply to the callback when you invoke it. This data replaces any user data specified when the timer was created.
Keywords
None.
Timer::Set
The Timer::Set method allows you to create an asynchronous timer.
You have two options available to create a timer. The first requires the specification of a callback procedure. The second requires an object that must have a method named handleTimerEvent.
In both cases user data is optional. The returned value is the timer's identifier, which can be used to cancel the timer or fire the timer early.
Note: Once IDL invokes the timer, its identifier is no longer valid (unless the REPEAT keyword is set). Namely, the <ID> cannot be used to cancel or fire the timer.
Syntax
ID = Timer.Set( Time, Callback [, UserData] [, /REPEAT] )
ID = Timer.Set( Time, Object [, UserData] [, /REPEAT] )
Return Value
Returns the ID (identifier) of the timer. Use this ID with the Timer::Cancel and Timer::Fire methods.
Arguments
Time
The amount of time in seconds until the timer should activate.
Callback
A string value specifying the name of the procedure that IDL should invoke when the timer activates.
The callback must have the following signature:
PRO Name, ID [, UserData]
Value |
Type |
Description |
Name
|
|
Name of the routine. When the timer is set using an object, this must be of the form <class>::handleTimerEvent. |
ID
|
Long |
The timer's identifier. This is the same value as returned by the "set" function.
|
UserData
|
Any
|
The data supplied when the timer was set. |
|
|
|
Object
Object on which to call a method named handleTimerEvent when the timer activates.
UserData
Data to be delivered to the callback. The delivered data will be a copy of the original. If user data is not supplied then the callback receives !NULL. UserData can be any valid IDL value. Examples of valid IDL values include: a constant, variable, expression, an array, etc.
Keywords
REPEAT
Set this keyword to create a repeating timer. By default, IDL will only fire a timer once. If REPEAT is set then IDL will fire the timer repeatedly until the timer is cancelled.
Timer::Unblock
The Timer::Block method allows you to unblock asynchronous timers that have been blocked.
Syntax
Timer.Unblock [, COUNT]
Arguments
None
Keywords
COUNT
Set this keyword to a named variable that will contain a long integer with the nesting level of the Block and Unblock methods.
Additional Information on Timers
Widget vs. Asynchronous Timers
The table below lays out the main differences between Widget-based and Asynchronous Timers:
Feature |
Widget Timers |
Asynchronous Timers |
Interrupt PRO code |
No |
Yes |
Interrupt C code |
No |
No |
Interrupt WAIT |
No |
No |
Interrupt callbacks |
No |
No |
Modal bases |
Will not fire until modal child dismissed. |
Fires even when modal base is present. |
Floating base |
Fires even when floating base is present. |
Fires even when floating base is present. |
Native dialogs* |
Will not fire until dialog is dismissed. |
Will not fire until dialog is dismissed. |
Blocking Xmanager |
No difference. |
No difference. |
Runtime/VM mode |
|
|
* The set of native dialogs include DIALOG_MESSAGE, DIALOG_PICKFILE, DIALOG_PRINTERSETUP, and DIALOG_PRINTJOB.
Resetting Timers
You can reset timers using any of the following actions:
Repeating Timers
You can create a repeating timer in two different ways:
Manually Trigger
In your callback routine, you can start another timer with the same time and callback. For example:
PRO myTimerCallback1, id, userData
COMPILE_OPT IDL2
. . . <do work> . . .
if (~done) then id = Timer.Set( 0.01, 'myTimerCallback1' )
END
PRO async_timer_example_repeat1
COMPILE_OPT IDL2
id = Timer.Set( 0.01, 'myTimerCallback1' )
END
Advantage: This way is simple to understand; the timer is guaranteed to not fire again until the callback has finished running.
Disadvantage: This way has an irregular interval. Even if you put the Timer.Set at the beginning of the callback, you are still limited by the IDL interpreter. In the above example, you would never get 100 callbacks per second, no matter how fast your callback code.
Automatically Repeating
To create a repeating timer you can set the REPEAT keyword when creating the timer. For example:
PRO myTimerCallback2, id, userData
COMPILE_OPT IDL2
. . . <do work> . . .
if (done) then void = Timer.Cancel(id)
END
PRO async_timer_example_repeat2
COMPILE_OPT IDL2
id = Timer.Set( 0.01, 'myTimerCallback2', /REPEAT )
END
Advantage: This technique is better for code that requires a regular interval, such as playing back a movie at 30 frames per second. In the above example, IDL will call your callback 100 times per second, regardless of how long your callback takes to run (as long as it's less than 0.01 seconds).
Disadvantage: If your callback takes a long time to execute, then the callbacks will be queued up and delivered when IDL is ready. If the number of queued callbacks exceeds the maximum (see Notes below) then the excess callbacks will be discarded. You should ensure that the execution time of your callback code does not exceed your repeat interval.
Notes on Using Timers
Version History
8.3 |
Introduced |
8.4 |
Added Block and Unblock methods; IDL no longer automatically interrupts callbacks.
|
8.5.2 |
Added REPEAT keyword to Timer.Set |
See Also
Static Methods