我正在编写一个简单的图像查看器,使用户可以快速浏览成千上万张图像,一次约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 mainloop's)内发生了ImageTk.PhotoImage对象的创建过程?创建PhotoImage在计算上是否昂贵,或者与从磁盘实际加载图像相比可以忽略不计?
第二种方法:
为了避开从Tkiner的mainloop线程中创建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)
问题是,由于产生了额外的开销(即,由于图像加载过程必须与GUI竞争,因此必须将图像加载过程分解为很小的块),Appart会在调用过程中定期冻结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')检查未决的键盘事件。这并不总是可靠的,并且界面仍然缓慢/无响应。
预先感谢您的任何想法
最佳答案
我建议创建一个load_one_image
函数而不是一个load_some_images
函数。它不太可能干扰事件循环。
同样,根据经验,通过after_idle
调用的函数不应使用after_idle
自行重新计划。原因是after_idle
将一直阻塞,直到耗尽空闲事件队列为止。如果您在队列处理过程中继续向队列中添加内容,则永远不会完全耗尽它。这可能是您的GUI似乎偶尔会因第二种方法挂起的原因。
尝试使用after(5, ...)
而不是after_idle(...)
。如果您的系统可以在不到5毫秒的时间内创建图像,则可以在半秒内处理100张图像,这可能足够快以提供漂亮的界面。您可以调整延迟,以查看延迟如何影响应用程序的整体感觉。
关于python - 与主循环一起运行Tkinter依赖代码,而无需GUI卡住,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18606229/