41.10 哨兵函数:检测进程状态变化

进程 哨兵函数(process sentinel) 是一个在关联进程因任何原因改变状态时被调用的函数,包括导致进程终止、暂停或继续运行的信号(无论是 Emacs 发送还是进程自身行为引发)。进程退出时也会调用哨兵函数。哨兵接收两个参数:发生事件的进程,以及描述事件类型的字符串。

如果进程未指定哨兵函数,将使用默认哨兵函数,它会在进程的缓冲区中插入一条包含进程名和事件描述字符串的消息。

描述事件的字符串类似以下形式(但并非完整列表):

哨兵函数仅在 Emacs 处于等待状态时运行(例如等待终端输入、时间流逝或进程输出)。这避免了在其他 Lisp 程序运行中途随机调用哨兵可能导致的时序错误。程序可以通过调用 sit-forsleep-for(see 等待时间流逝或输入)或 accept-process-output(see 接收进程输出)进入等待,从而让哨兵函数得以运行。命令循环读取输入时,Emacs 也允许哨兵运行。delete-process 在终止运行中的进程时会调用哨兵。

Emacs 不会为一个进程的多次状态变化维护队列,只记录当前状态和发生过变化这一事实。因此连续快速的两次状态变化可能只触发一次哨兵调用。不过进程终止一定会精确运行一次哨兵,因为终止后状态无法再改变。

Emacs 在运行进程哨兵前,会显式检查是否有来自该进程的输出。一旦因进程终止运行哨兵后,将不会再有来自该进程的输出。

将输出写入进程缓冲区的哨兵函数应当检查缓冲区是否仍然存活。如果试图向已死亡的缓冲区插入内容,会产生错误。如果缓冲区已死亡,(buffer-name (process-buffer process)) 会返回 nil

哨兵函数内部通常禁止退出操作,否则在命令层输入 C-g 或退出用户命令的效果会不可预测。如果希望在哨兵内允许退出,可将 inhibit-quit 绑定为 nil。大多数情况下,正确做法是使用宏 with-local-quit。See 退出

如果哨兵执行时发生错误,错误会被自动捕获,不会中断触发哨兵的正在运行的程序。但如果 debug-on-errornil,错误不会被捕获,从而可以使用 Lisp 调试器调试哨兵。See Lisp 调试器。若错误被捕获,Emacs 会暂停 process-error-pause-time 秒以便用户看到错误信息。See 创建异步进程

哨兵运行时,进程哨兵会被临时设为 nil,以防止哨兵递归调用。因此哨兵函数内部无法指定新的哨兵。

注意 Emacs 在执行哨兵时会自动保存和恢复匹配数据。See 匹配数据

Function: set-process-sentinel process sentinel

该函数将 sentinelprocess 关联。如果 sentinelnil,进程将使用默认哨兵,在进程状态变化时向缓冲区插入消息。

进程哨兵的修改会立即生效—如果哨兵已计划运行但尚未调用,此时设置新哨兵,最终执行时会使用新的哨兵。

(defun msg-me (process event)
   (princ
     (format "Process: %s had the event '%s'" process event)))
(set-process-sentinel (get-process "shell") 'msg-me)
     ⇒ msg-me
(kill-process (get-process "shell"))
     ⊣ Process: #<process shell> had the event 'killed'
     ⇒ #<process shell>
Function: process-sentinel process

该函数返回 process 的哨兵函数。

如果需要将进程状态变化传递给多个哨兵,可以使用 add-function 将现有哨兵与新哨兵组合。See 为 Emacs Lisp 函数添加建议

Function: waiting-for-user-input-p

在哨兵或过滤器函数运行时,如果 Emacs 在调用该函数时正等待用户键盘输入,则返回非 nil,否则返回 nil