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

Reply via email to