10199 Rate this article:
No rating

Using the TIMER class in IDL 8.3

Jim Pendleton

Prior to IDL 8.3, the WIDGET_TIMER event was the main mechanism for introducing asynchronous data processing to an IDL application.


This technique requires at least a minimal widget hierarchy with an event handler to process the events.  For desktop applications, this is not detrimental because most have UI components from which these events can originate.


In the context of ENVI Services Engine (ESE) on Linux where processing is taking place in the cloud, timer event-driven computation or monitoring requires that the host must be running an X Windows server process or a virtual frame buffer application, which is not always desired or possible. 


The TIMER static class in IDL 8.3 provides a new mechanism to manage asynchronous events in applications. Its main advantage is that it does not require IDL GUI widgets or an X server, making it particularly suitable for headless ENVI sessions within ESE.  Event processing with a TIMER is outside the context of IDL's usual widget event handling.


In its simplest form, a timer event is fired from code via


 id = TIMER.Set(delay, callback, user-data)


where


 id, the function return, is the unique identifier for the timer

 delay is the minimum time in seconds after which the event should be processed

 callback is a function (or object reference, discussed below) to be called

 user-data is any user-defined IDL variable or expression


For example

  id  = TIMER.Set(1., 'HandleTimerEvent', SYSTIME(1))


The event handler is an IDL procedure that accepts as input two arguments, the id of the TIMER and the reference to the user-data.


  PRO HandleTimerEvent, id, userdata
    PRINT, 'The timer executed in ', SYSTIME(1) - userdata, ' seconds'
  END


The TIMER fires a single-shot event after a specific interval. It does not make a new event call automatically after each delay.  If this type of behavior is desired, simply add a new TIMER call within your event handler, for example


  PRO HandleTimerEvent, id, userdata
    PRINT, 'The timer executed in ', SYSTIME(1) - userdata, ' seconds'
    id = TIMER.Set(1., 'HandleTimerEvent', SYSTIME(1))
  END


The delay specified in the call is a minimum time.  The delay in clock time may be longer.  Keep in mind that the timer callback runs in the same thread as the caller, generally the main IDL application thread or in the main thread of an IDL_IDLBridge.  If the CPU is busy with calculations on that thread or the interpreter is otherwise blocked at the point in time when the event handler is intended to be called, the callback will be deferred until the interpreter is free.


Let's take as a real world example a utility that monitors web pages for updates.


The main routine is straightforward.  Define a URL that will be monitored, and a HASH that will maintain our state. In the TIMER call, this HASH reference will be our user data.  The state will consist of the URL and the data returned from the URL request at a previous point in time, which upon initialization is blank.


PRO BlogMonitor
  URL = 'http://www.exelisvis.com/Company/'
  URL += 'PressRoom/Blogs/IDLDataPoint.aspx'
  h = HASH('url', URL, 'previous', '')
  id = TIMER.Set(60, 'HandleTimerEvent', h)
END


The delay of only 60 seconds is highly optimistic.  New articles on IDL Data Point are scheduled for weekly publication.


Next we construct a very simple event handler.  It will make a call to another function to check for updates, then fire a new timer event if desired.


PRO HandleTimerEvent, id, h
  IF (UpdateCheck(h)) THEN BEGIN
    id = TIMER.Set(60, 'HandleTimerEvent', h)
  ENDIF
END


We can make the comparison logic in the UpdateCheck function as simple or complicated as we want.  But for the sake of simplicity we will only check the number of lines returned in the URL request, and abstract that verification to another function, named Changed.


FUNCTION Changed, h, current
  RETURN, N_ELEMENTS(current) NE N_ELEMENTS(h['previous'])
END


The UpdateCheck function will get the HTML data from the website via IDLnetURL, call the Changed function for the comparison test and prompt the user to open the blog page in a web browser via SPAWN if there has been an update to the content.


The function returns 1 if TIMER should be fired again, or 0 if the user wants to cancel the monitoring process.


FUNCTION UpdateCheck, h
  status = 1
  current = (IDLnetURL()).Get(URL = h['url'], $
    /STRING_ARRAY)
  IF (Changed(h, current)) THEN BEGIN
    v = DIALOG_MESSAGE['Blog update available!', $
       'Do you want to view it?'], /QUESTION, /CANCEL)
    IF (STRCMP(v, 'Yes', /FOLD_CASE)) THEN BEGIN
       SPAWN, 'explorer.exe ' + h['url'], $
           /NOSHELL, /NOWAIT, /HIDE
    ENDIF ELSE IF (STRCMP(v, 'Cancel', /FOLD_CASE)) THEN BEGIN
        status = 0
    ENDIF
    h['previous'] = TEMPORARY(current)
  ENDIF
  RETURN, status
END


If the user clicks the Cancel button on the question dialog, the application will stop monitoring for changes.  If the user clicks the No button, the current web site's HTML will be cached but the website will not be displayed and monitoring will continue.  If the user clicks Yes, the data are cached, the web page is displayed, and monitoring continues.


This example was written for Windows platforms since it SPAWNs the standard OS "explorer.exe" application to view the web page. The code is simple to modify to launch a specific alternative browser.


An Object Oriented technique allows us to call Timer.Set with a reference to an object rather than the name of a callback procedure.  The object's class must have implemented a method with the name ::HandleTimerEvent that accepts the same two input parameters as the procedural form.


The object class shown below is a general purpose version of the procedural code we investigated earlier.  It accepts as optional keyword input the URL, delay time, and path to the web browser to be launched.


PRO BlogMonitor::HandleTimerEvent, id, dummy
  COMPILE_OPT IDL2
  IF (self.UpdateCheck()) THEN BEGIN
    id = TIMER.Set(self.h['delay'], self, !null)
  ENDIF ELSE OBJ_DESTROY, self
END

FUNCTION BlogMonitor::Changed, current
  COMPILE_OPT IDL2
  previous = self.h['previous']
  changed = N_ELEMENTS(current) NE N_ELEMENTS(previous)
  RETURN, changed
END

FUNCTION BlogMonitor::UpdateCheck
  COMPILE_OPT IDL2
  status = 1
  current = (self.h['net']).Get(URL = self.h['url'], $
    /STRING_ARRAY)
  IF (self.Changed(current)) THEN BEGIN
    v = DIALOG_MESSAGE(['There is a new blog update!', $
      'Do you want to view it?'], /QUESTION, /CANCEL)
    IF (STRCMP(v, 'Yes', /FOLD_CASE)) THEN BEGIN
      SPAWN, self.h['browser'] + ' ' + self.h['url'], $
        /NOSHELL, /NOWAIT, /HIDE
    ENDIF ELSE IF (STRCMP(v, 'Cancel', /FOLD_CASE)) THEN BEGIN
      status = 0
    ENDIF
    self.h['previous'] = TEMPORARY(current)
  ENDIF
  RETURN, status
END

PRO BlogMonitor::SetProperty, $
    URL = url, $
    BROWSER = browser, $
    DELAY = delay
IF (url NE !null) THEN self.h['url'] = url
IF (browser NE !null) THEN self.h['browser'] = browser
IF (delay NE !null) THEN self.h['delay'] = delay
END

FUNCTION BlogMonitor::Init, _Extra = Extra
  COMPILE_OPT IDL2
  URL = 'http://www.exelisvis.com/Company/'
  URL += 'PressRoom/Blogs/IDLDataPoint.aspx'
  self.h = HASH('url', URL)
  self.h += HASH('browser', 'explorer.exe')
  self.h += HASH('net', IDLnetURL())
  self.h += HASH('previous', '')
  self.h += HASH('delay', 1.)
  self.SetProperty, _Extra = Extra
  id = TIMER.Set(self.h['delay'], self, !null)
  RETURN, 1
END

PRO BlogMonitor__Define
!null = {BlogMonitor, Inherits IDL_Object, h : HASH()}
END


For example,


IDL> url = 'http://www.exelisvis.com/Default.aspx'

IDL> browser = '/usr/local/bin/firefox'

IDL> delay = 7*8.64e4

IDL> o = BlogMonitor(URL=url, BROWSER=browser, DELAY=delay)


Please login or register to post comments.