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 <niccokunzm...@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 <gu...@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@python.org > https://mail.python.org/mailman/listinfo/tkinter-discuss > > _______________________________________________ > Tkinter-discuss mailing list > Tkinter-discuss@python.org > https://mail.python.org/mailman/listinfo/tkinter-discuss > > > _______________________________________________ > Tkinter-discuss mailing list > Tkinter-discuss@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.uebernic...@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 _______________________________________________ Tkinter-discuss mailing list Tkinter-discuss@python.org https://mail.python.org/mailman/listinfo/tkinter-discuss