-1

I've seen numerous question on this site, where people ask how user-written code, say a function foo, can be executed in tkinter's mainloop, e.g. this or this. Two alternatives exist: Use the after method, or use threading. I'd like to know more about how the after method actually works.

More precisely, inspired by this excellent article, where a very high-level description of how the GIL in Python works is given, I'd like to know more how the after method works in terms of the Python interpreter processing foo inside tkinter's mainloop.
I'm particularly confused how the CPython interpreter steps through the code, when I insert code using after. How is foo ending up being executed by tkinter's mainloop?


What I found so far:

Bryan Oakley, quoted from the first link, says: "after does not create another thread of execution. Tkinter is single-threaded. after merely adds a function to a queue."

But inspecting the source code

def after(self, ms, func=None, *args):
    """Call function once after given time.

    MS specifies the time in milliseconds. FUNC gives the
    function which shall be called. Additional parameters
    are given as parameters to the function call.  Return
    identifier to cancel scheduling with after_cancel."""
    if not func:
        # I'd rather use time.sleep(ms*0.001)
        self.tk.call('after', ms)
    else:
        def callit():
            try:
                func(*args)
            finally:
                try:
                    self.deletecommand(name)
                except TclError:
                    pass
        callit.__name__ = func.__name__
        name = self._register(callit)
        return self.tk.call('after', ms, name)

doesn't really help me, as it doesn't reveal the answers to these questions, and I'm a novice programmer, so I don't really understand how to trace this further.

l7ll7
  • 1,309
  • 1
  • 10
  • 20
  • You need to trace this back to Tcl source code. – Nae Feb 17 '18 at 13:04
  • @Nae Does that mean that from the POV of CPython it will be a single statement? – l7ll7 Feb 17 '18 at 13:42
  • Well it's a code line that calls another code line which calls internal Tcl commands which returns when the GUI is closed, which would then return the function that calls Tcl commands, and when that returns the uppermost `mainloop` returns. – Nae Feb 17 '18 at 18:24

1 Answers1

0

I'd like to know more about how the after method actually works.

mainloop is just an endless loop which scans some internal queues to see if there are any events to process. Think of it as if it was implemented something like this:

def mainloop():
    while the_window_exists():
        if len(after_queue) > 0:
            event = after_queue.pop()
            if event.time_to_execute >= time.time():
                event.command(**event.args)
        if len(event_queue) > 0: 
            ...

It's not literally implemented that way -- it's a bit more efficient and there's a little more going on, but logically it's nearly identical.

When you call after, it simply puts something on the "after" queue. Nothing more, nothing less.

Using the same analogy, after might be implemented something like this:

def after(delay, code_to_run, *args):
    event = Event()
    event.code_to_run = code_to_run
    event.args = args
    event.time_to_execute = time.time() + delay
    event_queue.append(event)

That's all there is to it. You're putting something on a queue when you call after, and mainloop pulls things off of that queue. It all happens in the same thread.

To mainloop, the function you added with after is no different than a function that gets added when you move the mouse or press a button -- it's just an event object on a queue.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685