跳转至

在 IDL 8.3 中使用 TIMER 类

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/using-the-timer-class-in-idl-83

21255 为此文章评分:

暂无评分

在 IDL 8.3 中使用 TIMER 类

Jim Pendleton 2014年5月29日,星期四

在 IDL 8.3 之前,WIDGET_TIMER 事件是向 IDL 应用程序引入异步数据处理的主要机制。

此技术至少需要一个包含事件处理程序的最小部件层次结构来处理事件。对于桌面应用程序而言,这并无大碍,因为大多数应用程序都具有可以发起这些事件的用户界面组件。

然而,在 Linux 上的 ENVI 服务引擎环境中,处理是在云端进行的,基于定时器事件的计算或监控要求主机必须运行 X Window 服务器进程或虚拟帧缓冲应用程序,但这并不总是可取或可行的。

IDL 8.3 中的 TIMER 静态类提供了一种管理应用程序中异步事件的新机制。其主要优点是它不需要 IDL GUI 部件或 X 服务器,这使其特别适用于 ESE 中的无头 ENVI 会话。使用 TIMER 进行事件处理独立于 IDL 通常的部件事件处理机制。

其最简单的形式是,定时器事件通过以下代码触发:

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

其中:

id,函数的返回值,是定时器的唯一标识符。 delay 是最小等待时间(以秒为单位),在此之后应处理事件。 callback 是要被调用的函数(或对象引用,下文讨论)。 user-data 是任何用户定义的 IDL 变量或表达式。

例如:

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

事件处理程序是一个接受两个参数作为输入的 IDL 过程:定时器的 ID 和用户数据的引用。

PRO HandleTimerEvent, id, userdata
  PRINT, '定时器在', SYSTIME(1) - userdata, '秒后执行'
END

TIMER 在特定间隔后触发一次单次事件。它不会在每个延迟后自动进行新的事件调用。如果需要这种行为,只需在事件处理程序中添加一个新的 TIMER 调用即可,例如:

PRO HandleTimerEvent, id, userdata
  PRINT, '定时器在', SYSTIME(1) - userdata, '秒后执行'
  id = TIMER.Set(1., 'HandleTimerEvent', SYSTIME(1))
END

调用中指定的延迟时间是最小时间。实际的时钟延迟可能更长。请记住,定时器回调在与调用者相同的线程中运行,通常是主 IDL 应用程序线程或 IDL_IDLBridge 的主线程。如果 CPU 在该线程上忙于计算,或者在预定调用事件处理程序的时间点,解释器因其他原因被阻塞,则回调将被推迟,直到解释器空闲。

我们以一个实际例子来说明:一个监控网页更新的实用程序。

主程序很简单。定义一个要监控的 URL,以及一个用于维护状态的 HASH。在 TIMER 调用中,此 HASH 引用将是我们的用户数据。状态将包括 URL 和之前时间点从 URL 请求返回的数据,初始化时后者为空。

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

仅 60 秒的延迟是非常乐观的。IDL 数据点上的新文章计划每周发布一次。

接下来,我们构建一个非常简单的事件处理程序。它将调用另一个函数来检查更新,然后根据需要触发一个新的定时器事件。

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

我们可以让 UpdateCheck 函数中的比较逻辑尽可能简单或复杂。但为了简单起见,我们只检查 URL 请求返回的行数,并将该验证抽象到另一个名为 Changed 的函数中。

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

UpdateCheck 函数将通过 IDLnetURL 从网站获取 HTML 数据,调用 Changed 函数进行比较测试,并在内容有更新时通过 SPAWN 提示用户在 Web 浏览器中打开博客页面。

如果应再次触发 TIMER,则函数返回 1;如果用户想要取消监控过程,则返回 0。

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

如果用户点击问题对话框上的取消按钮,应用程序将停止监控更改。如果用户点击否按钮,当前网站的 HTML 将被缓存,但不会显示网站,并且监控将继续。如果用户点击是,数据将被缓存,网页被显示,并且监控继续。

此示例是针对 Windows 平台编写的,因为它通过 SPAWN 调用标准操作系统应用程序 "explorer.exe" 来查看网页。代码很容易修改以启动特定的替代浏览器。

面向对象技术允许我们使用对对象的引用(而不是回调过程的名称)来调用 Timer.Set。对象的类必须实现一个名为 ::HandleTimerEvent 的方法,该方法接受与过程形式相同的两个输入参数。

下面显示的对象类是我们之前研究的过程代码的通用版本。它接受 URL、延迟时间和要启动的 Web 浏览器路径作为可选关键字输入。

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

例如,

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)

连接 GIS 与遥感之间的鸿沟 LiDAR 数据的自定义处理:ENVI LiDAR API 示例