Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter after that survives clock rewinding

I noticed that in my version of Tkinter, the after() call does not survive system clock rewinding.

If the after(x, func) was called, and the system clock was rewinded, func will be called only after the clock returned to its time before the rewind + x milliseconds.

I assume this is because Tkinter uses the system-clock instead of the "time.clock" (the amount of time that the program is running).

I tested it only on windows, and maybe its because I have an old version of Tkinter. I want my App to work on computers that synchronize their clock from the network...

Does anyone have a simple solution?

like image 682
Oren Avatar asked Jun 18 '10 01:06

Oren


2 Answers

Unfortunately, neither Tkinter nor Tcl interpreter have an straightforward solution to your problem. The after(ms, func) method is based on the Tcl command of the same name, which creates an internal timer based on the current system time plus the amount of milliseconds passed as parameter.

In case you are curious, you can check it out directly from the 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);

Given this limitation, I would go for a pure Python approach, like using a 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()

It also has the advantage that runs on a separate thread, preventing not only blocking the GUI during the timer interval but also while executing the callback function.

like image 192
A. Rodas Avatar answered Oct 27 '22 16:10

A. Rodas


Explanation

As you suspected, when you use .after() tkinter is scheduling an event to happen at current_time + delay_ms. This means that if system time changes between those events, it will subvert the scheduled event.

This is based on the fact that tkinter is simply calling the after command of tcl (the underlying system that tkinter is communicating to). tcl docs tell us:

after uses the system time to determine when it is time to perform a scheduled event. This means that with the exception of after 0 and after idle, it can be subverted by changes in the system time.

Now notice that after idle is exempt from the system clock coupling as well as after 0 but those probably aren't good replacements for you.


after 0

This schedules a script for immediate execution. It's useful for getting the tightest possible event. Warning: This places the scheduled event at the front of the queue, so a command that perpeually reschedules itself in this manner can lock up the queue.

So using after 0 would not be optimal as it is in the front of the event queue meaning nothing else would happen.


after idle

This is similarly exempt but it probably won't be best either. [docs]

The script will be run exactly once, the next time the event loop is entered and there are no events to process.

So the script would run when the system next becomes idle. You could use after x with after idle and that would wait until the events are clear and the system is idle, then wait x milliseconds before running that command but I suspect that isn't what you want to do either.


Tl;dr

So to your final question, what can you do if the clock could be expected to be reversed? Native to tkinter: not much. Unless you caught the reverse happening and reset your after() event or writing your own event scheduler based on time.process_time() (formerly time.clock()), I don't see a way to have .after() perform differently.

like image 32
MyNameIsCaleb Avatar answered Oct 27 '22 17:10

MyNameIsCaleb