我正在编写一个简单的图像查看器,让用户可以非常快速地翻阅数万张图像,一次大约100张。图像是磁盘上的文件。
为了使观看者能够正常工作,它必须在用户当前的图像之前连续预加载图像(否则观看者会显得迟钝)。
我用来在Tkinter标签网格中显示图像的基本配方如下(已经过测试并且有效):
def load_image(fn):
image = Image.open(fn)
print "Before photoimage"
img = ImageTk.PhotoImage(image)
print "After photoimage"
label.config(image=load_image("some_image.png")
我需要ImageTk.PhotoImage实例来在标签上显示图像。我已经实现了两种不同的方法,每种方法都有相关的问题。
第一种方法: 启动一个预先加载图像的单独线程:
def load_ahead():
for fn in images:
cache[fn] = load_image()
threading.Thread(target=load_ahead).start()
top.mainloop()
这在我的Linux机器上运行得很好。但是,在另一台机器上(恰好运行Windows,并使用pyinstaller编译),似乎发生了死锁。打印“之前的Photoimage”,然后程序冻结,这表明加载程序线程在创建ImageTk.PhotoImage对象时卡住了。最好在主(Tkinter主循环)线程中发生ImageTk.PhotoImage对象的创建? PhotoImage的创建在计算上是否昂贵,或者与从磁盘实际加载图像相比可以忽略不计?
第二种方法: 为了避免在Tkiner的主循环线程中创建PhotoImage对象的这种可能要求,我使用了Tk.after:
def load_some_images():
#load only 10 images. function must return quickly to prevent freezing GUI
for i in xrange(10):
fn = get_next_image()
cache[fn] = load_image(fn)
top.after_idle(load_some_images)
top.after_idle(load_some_images)
这个问题是,appart从创建额外的开销(即图像加载过程必须分解成非常小的块,因为它与GUI竞争)它会在调用期间定期冻结GUI ,它似乎消耗了执行过程中发生的任何键盘事件。
第三种方法 有没有办法可以检测到待处理的用户事件?我怎样才能完成这样的事情?
def load_some_images():
while True:
try: top.pending_gui_events.get_nowait()
except: break
#user is still idle! continuing caching of images
fn = get_next_image()
cache[fn] = load_image(fn)
top.after_idle(load_some_images)
top.after(5,load_some_images)
编辑:我尝试使用top.tk.call('after','info')来检查挂起的键盘事件。这并不总是可靠的,并且界面仍然缓慢/无响应。
提前感谢任何想法
答案 0 :(得分:2)
我建议创建load_one_image
函数而不是load_some_images
函数。它不太可能干扰事件循环。
另外,根据经验,通过after_idle
调用的函数不应该使用after_idle
自行重新安排。原因是after_idle
将阻塞,直到空闲事件队列耗尽。如果在处理队列时继续向队列添加内容,则永远不会完全耗尽。这可能就是为什么你的GUI似乎偶尔会用你的第二种方法挂起的原因。
尝试after(5, ...)
而不是after_idle(...)
。如果你的系统可以在不到5ms的时间内创建一个图像,你可以在大约半秒内处理100个图像,这可能足够快,可以提供一个非常活泼的界面。您可以调整延迟,看看它如何影响应用程序的整体感觉。