Python 在mainloop旁边运行依赖于Tkinter的代码而不冻结GUI
我正在编写一个简单的图像查看器,它可以让用户快速浏览上万张图像,每次大约100张。这些图像是磁盘上的文件 为了让查看器正常工作,它必须在用户当前图像之前不断预加载图像(否则查看器将无法正常工作) 我用来在Tkinter标签的网格中显示图像的基本方法如下(这已经过测试并且有效): 我需要ImageTk.PhotoImage实例在标签上显示图像。我实现了两种不同的方法,每种方法都有一个相关的问题 第一种方法: 启动一个单独的线程预加载图像:Python 在mainloop旁边运行依赖于Tkinter的代码而不冻结GUI,python,multithreading,image,user-interface,tkinter,Python,Multithreading,Image,User Interface,Tkinter,我正在编写一个简单的图像查看器,它可以让用户快速浏览上万张图像,每次大约100张。这些图像是磁盘上的文件 为了让查看器正常工作,它必须在用户当前图像之前不断预加载图像(否则查看器将无法正常工作) 我用来在Tkinter标签的网格中显示图像的基本方法如下(这已经过测试并且有效): 我需要ImageTk.PhotoImage实例在标签上显示图像。我实现了两种不同的方法,每种方法都有一个相关的问题 第一种方法: 启动一个单独的线程预加载图像: def load_ahead(): for fn
def load_ahead():
for fn in images:
cache[fn] = load_image()
threading.Thread(target=load_ahead).start()
top.mainloop()
这在我的Linux机器上运行得很好。然而,在另一台机器上(恰好运行Windows,并使用pyinstaller编译),似乎发生了死锁。打印“Before Photoimage”,然后程序冻结,这表明加载程序线程在创建ImageTk.Photoimage对象时卡住了。在主线程(Tkinter mainloop的)内创建ImageTk.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)
这样做的问题是,由于产生额外的开销(即图像加载过程必须分解为非常小的块,因为它与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')检查挂起的键盘事件。这并不总是可靠的,接口仍然迟钝/无响应
提前感谢您的建议我建议您创建一个
加载一张图像
函数,而不是加载一些图像
函数。它不太可能干扰事件循环
此外,根据经验,在空闲后通过调用的函数不应在空闲后使用重新调度。原因是空闲后的将阻塞,直到空闲事件队列耗尽。如果在处理队列的过程中不断向队列中添加内容,它永远不会完全耗尽。这可能就是为什么您的GUI在使用第二种方法时偶尔会挂起的原因
在(5,…)
之后尝试,而不是在空闲(…)之后尝试
。如果您的系统可以在不到5毫秒的时间内创建一个图像,那么您可以在大约半秒钟内处理100个图像,这可能足够快,可以提供一个非常简洁的界面。您可以调整延迟,看看它如何影响应用程序的整体感觉。谢谢。我试过几种不同的方法。此方法引入的大量开销导致图像加载速度至少慢5倍。尽管如此,GUI仍然相当缓慢,忽略键盘事件并随机冻结数秒。即使我只加载一个映像,当加载一个较大的映像时,或者如果磁盘读取文件的速度较慢,用户也会不可避免地感受到这1-2秒或所需的时间。我认为这几乎排除了将映像的IO密集型加载放在主循环中的可能性。我尝试了第三种方法(使用self.tk.call('after','info')检查挂起的用户事件),这种方法可能更好一些,但仍然很慢且不可靠(事件并不总是很快被检测到)。使UI响应且不容易随机挂起的唯一方法似乎是在单独的线程上加载图像。@ealfonso:在_info
只告诉您在
之后安排的事件后,它对任何其他类型的事件一无所知。@ealfonso:图像有多大,您的计算机有多旧?在2013 macbook pro上,我可以在大约0.5秒内直接加载100张1300x600.gif图像。
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)