43.11 用于延迟执行的定时器

可以设置一个 定时器(timer),使其在指定的未来时间或经过一定空闲时长后调用某个函数。定时器是一种特殊对象,用于保存下次调用时间与待执行函数的相关信息。

Function: timerp object

该谓词函数在 object 为定时器时返回非 nil

Emacs 无法在 Lisp 程序的任意位置运行定时器,仅能在 Emacs 可接收子进程输出时运行:即处于等待状态时,或在 sit-forread-event 等可执行等待操作的特定原语函数内部。因此,若 Emacs 处于繁忙状态,定时器的执行可能被延迟。但在 Emacs 空闲时,执行时间会非常精确。

Emacs 在调用定时器函数前会将 inhibit-quit 绑定为 t,因为从多数定时器函数中退出可能导致程序状态不一致。这通常不会带来问题,因为大多数定时器函数不会执行大量操作。实际上,若定时器调用耗时较长的函数,往往会造成不良体验。若定时器函数需要允许退出,应使用 with-local-quit(see 退出)。例如,当定时器函数调用 accept-process-output 接收外部进程输出时,该调用应包裹在 with-local-quit 内,以确保外部进程挂起时 C-g 可以正常工作。

定时器函数修改缓冲区内容通常是不合适的。若确有必要,一般应在修改缓冲区前后均调用 undo-boundary,将定时器的修改与用户命令的修改分隔开,避免单个撤销记录变得过大。

定时器函数还应避免调用会使 Emacs 进入等待的函数,例如 sit-for(see 等待时间流逝或输入)。这可能导致不可预料的后果,因为等待期间其他定时器(甚至同一定时器)可能被触发。若定时器函数需要在一段时间后执行操作,可通过设置新定时器实现。

若定时器函数执行远程文件操作,可能与同一连接上正在执行的远程文件操作冲突。此类冲突会被检测并抛出 remote-file-error 错误(see 标准错误)。可通过将定时器函数体包裹在以下代码中进行防护:

(ignore-error 'remote-file-error
  ...)

若定时器函数调用的函数可能修改匹配数据,应保存并恢复匹配数据。See 保存与恢复匹配数据

Command: run-at-time time repeat function &rest args

该函数设置一个定时器,在时间 time 以参数 args 调用函数 function。若 repeat 为数值(整数或浮点数),定时器将在 time 之后每隔 repeat 秒重复执行。若 repeatnil,定时器仅执行一次。

time 可指定绝对时间或相对时间。

绝对时间可使用有限格式的字符串指定,均视为当日时间,即使该时间已过去。支持的格式包括 ‘xxxx’、‘x:xx’、‘xx:xx’(24 小时制),以及 ‘xxam’、‘xxAM’、‘xxpm’、‘xxPM’、‘xx:xxam’、‘xx:xxAM’、‘xx:xxpm’、‘xx:xxPM’。小时与分钟之间可使用点号代替冒号。

以字符串指定相对时间时,可在数字后跟随单位。例如:

1 min

表示从现在起 1 分钟后。

1 min 5 sec

表示从现在起 65 秒后。

1 min 2 sec 3 hour 4 day 5 week 6 fortnight 7 month 8 year

表示从现在起恰好 103 个月、123 天与 10862 秒后。

对于相对时间值,Emacs 将 1 个月视为严格 30 天,1 年视为严格 365.25 天。

并非所有便捷格式均为字符串。若 time 为数值(整数或浮点数),则表示以秒为单位的相对时间。encode-time 的返回值也可用于指定 time 的绝对时间。

多数情况下,repeat 不影响首次调用的时间,仅由 time 决定。唯一例外是当 timet 时,定时器将在从纪元开始每经过 repeat 秒的整数倍时执行。这对 display-time 等函数十分有用。例如,以下代码可使 function 在每个整分钟(如 ‘11:03:00’、‘11:04:00’ 等)执行:

(run-at-time t 60 function)

若定时器本应执行时 Emacs 未获得 CPU 时间(例如系统忙于运行其他进程,或计算机处于睡眠、挂起状态),定时器将在 Emacs 恢复并空闲后立即执行。

函数 run-at-time 返回一个定时器值,用于标识该次预定的后续操作。可使用该值调用 cancel-timer(见下文)。

Command: run-with-timer secs repeat function &rest args

该函数与 run-at-time 完全相同(参数说明参见该函数定义,secs 会作为 time 传递给对应函数),专用于以秒为单位指定延迟时间。

重复定时器理论上应每隔 repeat 秒执行一次,但需注意任意一次定时器调用都可能延迟。某次执行的延迟不会影响下一次预定执行时间。例如,若 Emacs 长时间繁忙计算,错过定时器三次预定执行,随后开始等待时,会连续立即调用三次定时器函数(假定其间无其他定时器触发)。若希望定时器在上次执行后至少间隔 n 秒再执行,不要使用 repeat 参数,而应在定时器函数中显式重新设置定时器。

User Option: timer-max-repeats

该变量用于指定当大量先前预定调用被不可避免地延迟时,连续重复调用定时器函数的最大次数。

Macro: with-timeout (seconds timeout-forms…) body…

执行 body,并在 seconds 秒后超时终止。若 body 在超时前执行完毕,with-timeout 返回 body 中最后一个表达式的值。若 body 的执行因超时而中断,with-timeout 将执行所有 timeout-forms 并返回其中最后一个表达式的值。

该宏通过设置一个在 seconds 秒后执行的定时器实现。若 body 在此之前执行完毕,则取消该定时器。若定时器实际触发,将终止 body 的执行并运行 timeout-forms

由于定时器仅能在 Lisp 程序调用可等待的原语时运行,with-timeout 无法在 body 处于计算过程中终止其执行,仅能在调用上述原语时生效。因此 with-timeout 仅适用于包含输入等待的 body,不适用于长时间计算。

函数 y-or-n-p-with-timeout 提供了一种简单方式,使用定时器避免过长时间等待用户应答。See Yes-or-No 查询

Function: cancel-timer timer

取消 timer 对应的预定操作,timer 应为一个定时器对象—通常是此前由 run-at-timerun-with-idle-timer 返回的对象。这会取消对应函数调用的效果;当指定时间到达时不会触发任何特殊操作。

list-timers 命令用于列出当前所有活动定时器。命令 ctimer-list-cancel)可取消光标所在行的定时器。可使用命令 Stabulated-list-sort)按列对列表排序。