《elisp 每日一练》——定时器机制

《elisp 每日一练》——定时器机制

Masimaro
2026-10-21 / 0 评论 / 1 阅读 / 正在检测是否收录...

在前面的一系列文章,我们介绍了hook和advice。现在我们开始进入下一个专题,关于定时器、外部进程调用的部分。Emacs本质上仍然是一个单线程同步的程序,一旦遇到比较耗时的操作就会卡死主界面。通过这一专题的相关技术,可以有效的缓解卡死的现象。

背景知识

我们首先要介绍的是里面的定时器机制。在常用的编程语言中我们都接触过定时器。它的使用非常简单,通过函数设置一个定时器,函数通常包括时间和回调。然后通过另一个函数启动定时器,在时间到达之后调用回调函数。

Emacs也是类似的。启动定时器的函数是 run-at-time。它的签名如下:

(run-at-time TIME REPEAT FUNCTION &rest ARGS)
  • TIME 是相对时间,单位是秒;
  • REPEAT 表示是否重复触发函数,如果为nil 触发一次并销毁,如果是秒数,则表示每间隔这段时间重复触发
  • FUNCTION 表示触发的函数,后面的 ARGS 是传递给函数的参数

函数会返回一个定时器对象,如果 REPEAT 参数是nil,在触发一次之后会自动销毁。或者我们可以随时手动调用 cancel-timer。销毁定时器

使用 run-at-time 定义的定时器是在调用开始之后计时。Emacs还有一种空闲计时器,它是在用户停止操作一段时间之后执行操作。它用来执行一些后台的耗时操作,在用户不频繁操作之后即使操作耗时导致界面卡住,它的用户体验也会稍微好一点。

这种定时器使用函数 run-with-idle-timer。函数签名如下:

(run-with-idle-timer SECS REPEAT FUNCTION &rest ARGS)

它的签名与 run-at-time 相同,但是要注意其中的 repeat 参数。run-at-time 中的 repeat 参数表示下一次经过多长时间后再触发,也是就说第一次触发后面触发的时间间隔可以不一样, repeat 参数实际上传入一个数值。但是 run-with-idle-timer 中没有这种机制, repeat 仅仅是一个bool 开关,用来表示下次是否重复。它不能用来控制下一次触发的时间间隔。

练习

我们来实现一个定时提醒喝水的功能,使用定时器定义一个时间,当时间到达之后提示用户该喝水休息了。当用户休息时,我们使用空闲定时器触发一个任务,统计当前编辑的buffer中的字数

(defvar my-hydration-timer nil
  "定时喝水定时器对象")

(defvar my-idle-audit-timer nil
  "用户空闲时,静默统计字数的定时器")

(defun my-hydration-alert-callback()
  "定时触发提示,提醒用户该喝水了"
  (message "已经连续工作一段时间了,喝口水休息一下把"))

(defun my-start-hydration-timer()
  "开启一个每隔30秒触发一次的循环提醒定时喝水"
  (interactive)
  (when my-hydration-timer
    (cancel-timer my-hydration-timer))
  (setq my-hydration-timer (run-at-time 30 30 #'my-hydration-alert-callback)))

(defun my-silent-heavy-audit()
  "定时器回调函数,会统计当前字数并输出"
  (when (derived-mode-p 'text-mode 'prog-mode)
    (let ((word-count (count-words-region (point-min) (point-max))))
      (message "当前buffer 中共 %d 个字" word-count))))

(defun my-start-idle-audit-timer()
  "开启空闲定时器, 用户停止输入2s后会触发"
  (interactive)
  (when my-idle-audit-timer
    (cancel-timer my-idle-audit-timer))
    (setq my-idle-audit-timer (run-with-idle-timer 2.0 t #'my-silent-heavy-audit)))

总结

我们今天主要介绍了两种定时器,一种常规的定时器用 run-at-time 启动。另一种是空闲定时器,用户空闲达到一定时间时触发,它使用 run-with-idle-timer 触发。

0

评论 (0)

取消