[Tkinter-discuss] Waking up the Tk event loop from a thread

Jan Uebernickel jan.uebernickel at softdevel.de
Wed Nov 6 19:06:44 CET 2013


Hi,

afaik the after() / after_idle() calls are not thread safe!

On my research leading to the code Andreas provide I found the
event_generate() method the only threadsafe way to invoke the tk
thread without letting it poll something. (As mentioned by Guido and
seen on other pages everyone else seems to poll... why?)

brgds,

-- Jan

2013/11/6 Nicco Kunzmann <niccokunzmann at rambler.ru>:
> Hello Guido van Rossum,
>
> when your source code looks like this:
>
>     from tkinter import Tk, Label
>     from threading import Thread
>     import time
>     def thread():
>         for i in range(10):
>             time.sleep(1)
>             t.after(0, lambda: l.configure(text = str(i)))
>     t = Tk()
>     l = Label(t)
>     l.pack()
>     th = Thread(target = thread)
>     th.start()
>     t.mainloop()
>
> then it works under Windows.
>
> Thank you for Python,
> Nicco Kunzmann
>
> Am 06.11.2013 18:17, schrieb Andreas Ostermann:
>
> Hi,
>
> some time ago we were very unhappy about the same issue.
>
> Coming from Java I tried to port the "invokeLater" idea of swing to
> python which leads me to the following code (including dynamic Proxy
> objects for easier communication between the stuff in the background
> and the tk world) [the Background thread in this example is doing some
> polling, I later used an own implementation for a Queue which let me
> interrupt the get... but as your problem is the tk world: there is no
> busy waiting in this example]
>
> import Tkinter
> import Queue
> import uuid
> import threading
> import thread
>
> # ### ### ### ### ### ### ### ### ###  ### ### ### ### ### ### ### ###
> ###  ### ### ### ### ### ### ### ### ###
>
> class BackgroundListener (threading.Thread) :
>     def __init__(self):
>         threading.Thread.__init__(self)
>         self.queue = Queue.Queue()
>         self.cancel = threading.Event()
>     def invokeLater(self, delegate):
>         self.queue.put(delegate)
>     def interrupt(self) :
>         self.cancel.set()
>     def run(self):
>         while not self.cancel.isSet() :
>             try :
>                 delegate = self.queue.get(timeout=0.5)
>                 if not self.cancel.isSet() : # don't call if already
> finished !
>                     delegate()
>             except Queue.Empty :
>                 pass
>
> class TkListener :
>     def __init__(self, tk) :
>         self.queue = Queue.Queue()
>         self.tk = tk
>         self.event = "<<%s>>" % uuid.uuid1()
>         tk.bind(self.event, self.invoke)
>     def invokeLater(self, delegate) :
>         self.queue.put(delegate)
>         self.tk.event_generate(self.event, when='tail')
>     def invoke(self, event) :
>         try :
>             while True :
>                 delegate = self.queue.get(block=False)
>                 delegate()
>         except Queue.Empty :
>             pass
>
> # ### ### ### ### ### ### ### ### ###  ### ### ### ### ### ### ### ###
> ###  ### ### ### ### ### ### ### ### ###
>
> class Delegate :
>     def __init__(self, real, name, args, kwargs) :
>         self.real = real
>         self.name = name
>         self.args = args
>         self.kwargs = kwargs
>     def __call__(self) :
>         method = getattr(self.real, self.name)
>         apply(method, self.args, self.kwargs)
>
> class Delegator :
>     def __init__(self, listener, real, name) :
>         self.listener = listener
>         self.real = real
>         self.name = name
>     def __call__(self, *args, **kwargs) :
>         delegate = Delegate(self.real, self.name, args, kwargs)
>         self.listener.invokeLater(delegate)
>
> class Proxy :
>     def __init__(self, listener, real) :
>         self.listener = listener
>         self.real = real
>         self.cache = {}
>     def __getattr__(self, name) :
>         try :
>             delegator = self.cache[name]
>         except KeyError :
>             delegator = Delegator(self.listener, self.real, name)
>             self.cache[name] = delegator
>         return delegator
>
> # ### ### ### ### ### ### ### ### ###  ### ### ### ### ### ### ### ###
> ###  ### ### ### ### ### ### ### ### ###
>
> class MyTkinterObject:
>     def __init__(self, tk):
>         frame = Tkinter.Frame(tk)
>         self.quitButton = Tkinter.Button(frame, text="QUIT", fg="red",
> command=frame.quit)
>         self.quitButton.pack(side=Tkinter.LEFT)
>         self.helloButton = Tkinter.Button(frame, text="Hello")
>         self.helloButton.pack(side=Tkinter.LEFT)
>         self.hello2Button = Tkinter.Button(frame, text="Hello2",
> command=self.trigger2)
>         self.hello2Button.pack(side=Tkinter.LEFT)
>         frame.pack()
>     def register(self, myBackgroundObject) :
>         self.helloButton.bind("<Button-1>", myBackgroundObject.trigger)
>     def trigger(self, callback) :
>         print "%s - Front hello" % thread.get_ident()
>         callback()
>     def trigger2(self) :
>         print "%s - Front hello" % thread.get_ident()
>
> class MyBackgroundObject :
>     def __init__(self, listener):
>         self.proxy = Proxy(listener, self)
>     def register(self, myTkinterObject) :
>         self.myTkinterObject = myTkinterObject
>     def trigger(self, event) :
>         print "%s - Back hello - %s" % (thread.get_ident(), event)
>         self.myTkinterObject.trigger(self.proxy.callback)
>     def callback(self) :
>         print "%s - Back bye" % thread.get_ident()
>
> # ### ### ### ### ### ### ### ### ###  ### ### ### ### ### ### ### ###
> ###  ### ### ### ### ### ### ### ### ###
>
> def main() :
>     root = Tkinter.Tk()
>
>     tkListener = TkListener(root)
>     backgroundListener = BackgroundListener()
>
>     myTkinterObject = MyTkinterObject(root)
>     myBackgroundObject = MyBackgroundObject(backgroundListener)
>
>     myTkinterObject.register(myBackgroundObject.proxy)
>     myBackgroundObject.register(Proxy(tkListener, myTkinterObject))
>
>     backgroundListener.start()
>     root.mainloop()
>     backgroundListener.interrupt()
>
> # ### ### ### ### ### ### ### ### ###  ### ### ### ### ### ### ### ###
> ###  ### ### ### ### ### ### ### ### ###
>
> if __name__ == "__main__":
>     main()
>
> brgds,
>
> -- Jan Übernickel (via Andreas)
>
> 2013/11/6 Guido van Rossum <guido at python.org>:
>
> After many years I'm trying my hands at Tkinter again, and things have
> changed...
>
> I'm using Python 2.7 here, but it doesn't look like 3.x changes the issue.
> Tcl/Tk version is 8.5, on Mac OS (10.8).
>
> I've got a situation where a background thread is doing I/O and wants to
> wake up the Tk event loop. It seems the established wisdom (e.g.
> http://code.activestate.com/recipes/82965-threads-tkinter-and-asynchronous-io/)
> is to have the Tk event loop wake up every e.g. 100 msec and check a queue
> or other state shared with the thread. This works, but makes me worry that
> my app will be draining battery power even when no data from the bg thread
> is forthcoming.
>
> I've tried other things:
>
> - createfilehandler: This says it cannot work when Tcl/Tk is threaded and it
> will be removed in Python 3 anyway.
>
> - The bg thread can use after_idle() or after() to run a command on the Tk
> object; this works, but it doesn't seem to wake up the Tk main loop -- the
> command usually only runs when I cause a UI event to be generated (e.g.
> selecting the window).
>
> What other solutions could I try? I really don't like the "after(100,
> <repeat>)" solution.
>
> --
> --Guido van Rossum (python.org/~guido)
>
> _______________________________________________
> Tkinter-discuss mailing list
> Tkinter-discuss at python.org
> https://mail.python.org/mailman/listinfo/tkinter-discuss
>
> _______________________________________________
> Tkinter-discuss mailing list
> Tkinter-discuss at python.org
> https://mail.python.org/mailman/listinfo/tkinter-discuss
>
>
> _______________________________________________
> Tkinter-discuss mailing list
> Tkinter-discuss at python.org
> https://mail.python.org/mailman/listinfo/tkinter-discuss
>



-- 
Jan Übernickel
Dipl.-Ing. (FH) Technische Informatik
Dipl.-Inform. (FH)

Softdevel GmbH - Don't limit your fantasies.
Müncheberg 10
24594 Hohenwestedt

Tel.: +49-4871-9919-797
Fax: +49-4871-9919-798
Mobil: +49-176-6323-6068
jan.uebernickel at softdevel.de
http://www.softdevel.de

Registergericht / Court of Registry: Amtsgericht Kiel
HRB Nr. / Commercial Register No.: 10801 KI
Geschäftsführer / Executive Director; Michael Hoppe


More information about the Tkinter-discuss mailing list