Tuesday, 3 September 2013

Running Tkinter dependent code alongside mainloop without GUI freeze

Running Tkinter dependent code alongside mainloop without GUI freeze

I am writing a simple image viewer that lets the user flip very quickly
through tens of thousands of images, about 100 at a time. The images are
files on disk.
In order for the viewer to function, I must continuously preload images
ahead of the user's current one (or the viewer would be unusably
sluggish).
The basic recipe that I'm using to display the images in a grid of Tkinter
labels, is the following (this has been tested and works):
def load_image(fn):
image = Image.open(fn)
print "Before photoimage"
img = ImageTk.PhotoImage(image)
print "After photoimage"
label.config(image=img)
I need the ImageTk.PhotoImage instance to display the image on a label.
I have implemented two different approaches, each with an associated problem.
First approach: Launch a separate thread which pre-loads the images:
def
load_ahead():http://orangesquash.org.uk/~laney/blog/posts/2011/10/mono-gotcha/
for image_fn in images:
global_image_dict[image_fn] = load_image()
threading.Thread(target=load_ahead).start()
top.mainloop()
This works quite well on my Linux machine. However, on another machine
(which happens to be running Windows, and compiled with pyinstaller), a
deadlock seems to happen. "Before Photoimage" is printed, and then the
program freezes, which suggests that the loader thread gets stuck at
creating the ImageTk.PhotoImage object. Musst the creation of an
ImageTk.PhotoImage object happen within the main (Tkinter mainloop's)
thread? Is the creation of PhotoImage computationally expensive, or is
negligible compared to actually loading the image from disk?
Second approach: In order to circumvent this possible requirement of
PhotoImage objects being created from within Tkiner's mainloop thread, I
resorted to Tk.after:
def load_some_images():
#load only 10 images. function must return quickly to prevent freezing
GUI
for i in xrange(10):
next_image = get_next_image()
global_image_dict[next_image] = load_image(next_image)
top.after_idle(load_some_images)
top.after_idle(load_some_images)
The problem with this is that, appart from creating additional overhead
(ie the image-loading procedure must be broken up into very small chunks
since it is competing with the GUI) that it periodically freezes the GUI
for the duration of the call, and it seems to consume any keyboard events
that happened during its execution. Is there a way I can detect pending
user events? How can I accomplish something like this?
def load_some_images():
while True:
try: pending_gui_event.get_nowait()
except: break
#user is still idle. continuing caching images
next_image = get_next_image()
global_image_dict[next_image] = load_image(next_image)
top.after_idle(load_some_images)
top.after_idle(load_some_images)
Thanks in advance for any ideas

No comments:

Post a Comment