Hi to all, I use python on win32 and since on windows the multithread support in the tk/tcl does not work at all i have to rewrite the graphical part of my application as a single thread is there anyone that can give me some tips (some src code samples )on how to write a graphical application with the entire graph manged by the main thread (the one that does mainloop() ) and with some threads that comunicate with it by the queue mechanism ? thanks , Francesco
I'm not in your situation, so I've never actually had to address this problem. However, I thought about how I'd solve it. First, one way to do it is to re-structure your code so that you no longer use threads. Any long-running calculations are re-structured so that they can run in pieces from Tk's "after", blocking the user interface for no more than a few ms at a time. This is often far from trivial, even when using the "generators as limited coroutines" approach. If you can't do that, then you have to arrange for GUI calls in a non-main thread to actually be sent to the main thread somehow. The obvious way is through a Queue.Queue. In the main thread, an "after" will fire every 100ms or so, service any items in the queue of calls, and then return to normal event handling. There are a few more wrinkles: Some calls will want the return value, others won't. Some calls will need to be done in a bundle, without handling events in the meantime. Here's the class I wrote, called TkThreadingGizmo: #------------------------------------------------------------------------ # Copyright (C) 2004 Jeff Epler # You may use this code according to the terms of the Python License import Queue class TkThreadingGizmo: def __init__(self, csize=100, rsize=1): self.calls = Queue.Queue(csize) self.results = Queue.Queue(rsize) self.block = False def install(self, window=None, interval=100): if window is None: window = Tkinter._default_root self.window = window self.interval = interval self.handle_calls_callback() def unuinstall(self): self.window = None def queue_call(self, func, *args, **kw): self.calls.put((True, func, args, kw)) state, value = self.results.get() if state: raise value else: return value def queue_call_async(self, func, *args, **kw): self.calls.put((False, func, args, kw)) def handle_calls(self): while self.block or not self.calls.empty(): wantresult, func, args, kw = self.calls.get() if wantresult: try: self.results.put((False, func(*args, **kw))) except (KeyborardInterrupt, SystemExit): raise except Exception, detail: self.results.put((True, detail)) else: try: func(*args, **kw) except (KeyborardInterrupt, SystemExit): raise except Exception, detail: exception.print_last() def handle_calls_callback(self): self.handle_calls() if self.window: self.window.after(self.interval, self.handle_calls_callback) #------------------------------------------------------------------------ The important details: queue_call sends a call from a thread to the main thread, and waits for the result. Most exceptions are intended to be reraised in the caller, but this is untested. queue_call_async sends a call from a thread to the main thread, but doesn't wait for a result. Most exceptions are intended to be printed (untested, again) but some exceptions are allowed to propagate in the main thread. Set the "block" flag *before* the first call that must happen in a bundle, then unset it *before* the last call (not *after* the last call). Here's a test program: #------------------------------------------------------------------------ # Copyright (C) 2004 Jeff Epler # You may use this code according to the terms of the Python License from Tkinter import * from tktg import TkThreadingGizmo import threading import time class App(threading.Thread): def run(self): i = 0 while not self.shutdown: self.block = True value = self.gizmo.queue_call(self.var.get) self.block = False self.gizmo.queue_call_async(self.var.set, value + 1) time.sleep(1) t = Tk() v = IntVar(t) l = Label(t, text="Value:", width=8) l.pack(side="left") l = Label(t, textvariable=v, width=5) l.pack(side="left") b = Button(t, text="Reset", width=8, command=lambda: v.set(0), default="active") t.bind("<Return>", lambda event: b.invoke()) b.pack(side="left") g = TkThreadingGizmo() a = App() a.gizmo = g; a.var = v; a.shutdown = 0 a.start() g.install(t) try: t.mainloop() finally: a.shutdown = 1 #------------------------------------------------------------------------ The thread just increments the value once a second or so. Because it reads the old value, and then sets the new value, the calls must be done as a bundle. Otherwise, you would be able to hit the Reset button between the read and the write, so that the value would jump to 0 for a moment but then return to the previous value plus one. Feel free to use this code if it's helpful to you. Jeff
participants (2)
-
francesco.grillo@ciaolab.com -
Jeff Epler