Tcl/Tk倒计时器取消after延迟命令是否存在竞态条件
Tcl/Tk 带重置功能倒计时器的竞态条件疑问
实现代码
我实现了一款带「Reset(重置)」按钮的倒计时器,支持通过该按钮终止正在运行的倒计时流程,实现代码如下:
set LAUNCHTIME 5 set message $LAUNCHTIME set afterId {} proc countdown {remainingSeconds} { global afterId message # ** if {$remainingSeconds > 0} { set message $remainingSeconds # ** set afterId [after 1000 [list countdown [expr {$remainingSeconds - 1}]]] } else { set message "Takeoff!" .start configure -state normal } } label .message -textvariable message button .start -text Start -command { .start configure -state disabled countdown $LAUNCHTIME } button .reset -text Reset -command { # Reset timer, message, and button. after cancel $afterId ;# <-- XXX: Could this cause a race condition? set message $LAUNCHTIME .start configure -state normal } grid .message grid .start grid .reset grid anchor . center
运行效果
按下「Start(启动)」按钮后的运行效果参考:
问题描述
我想确认:倒计时运行过程中按下Reset按钮是否可能触发竞态条件?
我担忧的具体时序场景:若程序执行到上述代码中双星号(**)标记的代码区间时用户按下Reset按钮,此时不存在已调度的待执行延迟命令,Reset操作中的after cancel $afterId无法取消任何延迟任务;但程序执行完该区间代码后会立即调度新的延迟命令(对应set afterId [after 1000 ...]语句),导致倒计时继续运行,违背用户重置倒计时的操作预期。
解答
你担忧的这个竞态条件在当前代码的运行逻辑下完全不会发生,核心原因是标准Tcl/Tk采用单线程串行事件循环模型,不存在脚本执行和UI事件抢占运行的情况:
- 所有UI回调(包括按钮点击触发的Reset命令)、
after调度的延迟任务,都必须等当前正在执行的同步脚本完全运行结束、控制权交还给事件循环后,才会按入队顺序依次处理。 - 你标记的
**区间属于countdown过程内的同步执行代码,这段代码运行全程不会响应任何外部事件:即使用户在这期间点击了Reset按钮,点击事件只会被暂存在事件队列中,必须等本次countdown执行完全部逻辑——也就是完成下一次1秒延迟任务的调度、给afterId赋上新值、过程返回之后,Reset按钮的回调才会被取出执行。 - 此时Reset回调执行
after cancel $afterId时,拿到的就是刚调度完成的新延迟任务ID,可以正常取消对应任务,完全不会出现倒计时偷偷继续运行的问题。
唯一可能触发你担心的问题的场景,是后续你在countdown过程的**区间内主动加入了update、vwait这类会强制嵌套进入事件循环的命令,这类命令会在同步脚本执行中途临时拉起事件循环处理待响应事件,才可能出现漏取消的情况,你当前给出的代码没有这类调用,不存在竞态风险。
内容的提问来源于stack exchange,提问作者Flux
相关产品推荐
相关产品推荐

