[Tkinter-discuss] tk problem !!!
Jeff Epler
jepler at unpythonic.net
Thu Oct 21 20:07:04 CEST 2004
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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://mail.python.org/pipermail/tkinter-discuss/attachments/20041021/0467685a/attachment.pgp
More information about the Tkinter-discuss
mailing list