Tkinter在时钟倒退后仍能生存

10 投票
2 回答
704 浏览
提问于 2025-04-16 00:07

我发现我用的Tkinter版本中,after()这个调用在系统时钟被倒回时不会继续有效。

也就是说,如果你调用了after(x, func),然后系统时钟被倒回了,func这个函数只有在时钟回到倒回之前的时间加上x毫秒后才会被调用。

我猜这是因为Tkinter使用的是系统时钟,而不是“time.clock”(也就是程序运行的时间)。

我只在Windows上测试过,也许是因为我用的是旧版的Tkinter。我希望我的应用能在那些从网络同步时钟的电脑上正常工作……

有没有人有简单的解决办法?

2 个回答

1

解释

正如你所怀疑的,当你使用 .after() 时,tkinter 实际上是在安排一个事件在 current_time + delay_ms 的时候发生。这意味着,如果在这段时间内系统时间发生了变化,就会影响这个安排的事件。

这是因为 tkinter 只是调用了 tcl 的 after 命令(tkinter 背后与之沟通的系统)。tcl 文档告诉我们:

after 使用系统时间来判断何时执行安排的事件。这意味着除了 after 0 和 after idle,它会受到系统时间变化的影响。

现在注意到 after idleafter 0 不受系统时钟的影响,但这两个可能并不是你想要的好替代方案。


after 0

这个命令会立即安排一个脚本执行。它对于获取最紧凑的事件非常有用。警告:这会把安排的事件放在队列的最前面,所以一个不断自我安排的命令可能会导致队列被锁住。

所以使用 after 0 并不是最优选择,因为它在事件队列的最前面,这意味着其他事情都不会发生。


after idle

这个命令同样不受系统时钟的影响,但它可能也不是最佳选择。[文档]

这个脚本会在下次进入事件循环且没有事件需要处理时执行一次。

所以这个脚本会在系统下次变得空闲时运行。你可以使用 after xafter idle 结合,这样会等到事件处理完毕且系统空闲后,再等待 x 毫秒再执行那个命令,但我怀疑这也不是你想要的。


总结

所以针对你的最后一个问题,如果时钟可能会被倒转,你能做什么?对于 tkinter 来说,能做的并不多。除非你能捕捉到时间被倒转的情况并重置你的 after() 事件,或者自己写一个基于 time.process_time()(以前是 time.clock())的事件调度器,否则我看不出有什么办法让 .after() 的表现有所不同。

6

很遗憾,Tkinter和Tcl解释器都没有简单直接的方法来解决你的问题。after(ms, func)这个方法是基于同名的Tcl命令,它会根据当前系统时间加上你传入的毫秒数来创建一个内部定时器。

如果你感兴趣,可以直接查看Tcl/Tk的源代码

Tcl_GetTime(&wakeup);
wakeup.sec += (long)(ms / 1000);
wakeup.usec += ((long)(ms % 1000)) * 1000;
if (wakeup.usec > 1000000) {
    wakeup.sec++;
    wakeup.usec -= 1000000;
}
afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup,
    AfterProc, afterPtr);

鉴于这个限制,我建议使用纯Python的方法,比如使用定时器

import time
import threading
import tkinter as tk

root = tk.Tk()

def say_hi():
    print(time.perf_counter(), "-", "Hi after 30sec!")
    root.destroy()

print(time.perf_counter(), "-", "Waiting 30sec")
threading.Timer(30, say_hi).start()
root.mainloop()

这个方法还有一个好处,就是它在一个单独的线程上运行,这样不仅可以避免在定时器间隔内阻塞图形界面,还能在执行回调函数时也不会阻塞。

撰写回答