我注意到在我的Tkinter版本中,after()调用不能在系统时钟倒带中存活。
如果调用了after(x,func),并且系统时钟被重新启动,则只有在时钟返回到倒带+ x毫秒之前的时间后才会调用func。
我假设这是因为Tkinter使用系统时钟而不是“time.clock”(程序运行的时间)。
我只在Windows上测试它,也许是因为我有一个旧版本的Tkinter。我希望我的应用程序可以在与网络同步时钟的计算机上运行...
有人有一个简单的解决方案吗?
不幸的是,Tkinter和Tcl解释器都没有直接解决您的问题。 after(ms, func)
方法基于同名的Tcl命令,它根据当前系统时间加上作为参数传递的毫秒数创建内部计时器。
如果您感到好奇,可以直接从Tcl/Tk source code查看:
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方法,比如使用Timer:
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()
它还具有在单独的线程上运行的优点,不仅可以防止在定时器间隔期间阻止GUI,还可以在执行回调函数时阻止GUI。
正如您所怀疑的,当您使用.after()
时,tkinter会安排在current_time + delay_ms
发生的事件。这意味着如果系统时间在这些事件之间发生变化,它将颠覆预定的事件。
这是基于tkinter只是调用tcl的after
命令(tkinter正在与之通信的底层系统)的事实。 tcl docs告诉我们:
在使用系统时间之后确定何时执行预定事件。这意味着除了0之后和空闲之后,它可以被系统时间的变化破坏。
现在注意after idle
不受系统时钟耦合以及after 0
的限制,但那些可能不适合你。
这会安排脚本立即执行。它对于获得最严重的事件非常有用。警告:这会将计划的事件放在队列的前面,因此以这种方式永久重新计划自身的命令可以锁定队列。
所以使用after 0
并不是最优的,因为它位于事件队列的前面,这意味着什么都不会发生。
这同样是免税的,但也可能不是最好的。 [docs]
该脚本将在下次输入事件循环并且没有要处理的事件时运行一次。
因此,当系统下一个空闲时,脚本将运行。您可以将after x
与after idle
一起使用,并等到事件清除且系统处于空闲状态,然后在运行该命令之前等待x
毫秒,但我怀疑这不是您想要做的。
Tl;博士
那么对于你的最后一个问题,如果时钟可以被推翻,你会怎么做?原生于tkinter:不多。除非你发现相反的情况并重置你的after()
事件或编写你自己的事件调度程序基于time.process_time()
(以前的time.clock()
),我没有看到让.after()
表现不同的方法。