如何在Tkinter Python中使用线程?
在Python的GUI编程中,Tkinter是一个不错的选择,它提供了很多可视化组件和工具。然而,在GUI编程中处理任务会让UI变得不响应,因此我们需要使用多线程以使GUI在处理任务时不会被阻塞。在本文中,我们将介绍如何在Tkinter Python中使用线程。
多线程基础
在Python中,我们可以使用 threading
模块来创建和管理线程。以下是一个简单的多线程示例,该示例创建了两个线程来打印数字 1 至 10。
import threading
def print_nums():
for i in range(1, 11):
print(i)
if __name__ == '__main__':
t1 = threading.Thread(target=print_nums)
t2 = threading.Thread(target=print_nums)
t1.start()
t2.start()
t1.join()
t2.join()
我们通过 target
参数指定要在线程内执行的函数,然后使用 start()
方法启动新线程。在这个示例中,我们创建了两个线程,并让它们打印数字 1 至 10。最后,我们使用 join()
方法等待所有线程完成。
Tkinter 中的线程问题
在Tkinter中使用线程时,有一个非常重要的问题需要处理:线程修改了Tkinter组件的值,而Tkinter不允许非主线程修改组件的值。因此,我们不能直接在线程内修改Tkinter组件,否则会出现异常。
import threading
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None,):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello World\n(click me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack(side="top")
self.quit = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.quit.pack(side="bottom")
def say_hi(self):
self.hi_there["text"] = "Hello World!"
def long_running_task():
import time
for i in range(1, 11):
print(i)
time.sleep(1)
app.hi_there["text"] = "Task Finished!"
if __name__ == '__main__':
root = tk.Tk()
app = App(master=root)
app.mainloop()
t1 = threading.Thread(target=long_running_task)
t1.start()
在上述代码中,我们在Thread内部调用了 app.hi_there
的 text
属性。当运行这个程序时,我们将会得到如下错误:
_tkinter.TclError: main thread is not in main loop
这个异常表明,非主线程不能直接修改Tkinter组件的值。
解决方案
因此,我们需要在Tkinter的主循环(mainloop)中运行线程以避免这个问题。这可以通过使用 after()
方法在Tkinter中利用递归实现。
def long_running_task():
import time
for i in range(1, 11):
print(i)
time.sleep(1)
app.after(0, lambda: app.hi_there.config(text="Task Finished!"))
在这个示例中,我们将 app.hi_there.config(text="Task Finished!")
包装在一个lambda函数内,然后使用 after()
方法延迟0微秒来调用这个lambda函数。这样,我们就可以在主循环内运行线程,同时在执行任务结束后更新Tkinter组件。
如果任务需要更新Tkinter组件的值,则必须使用 after()
方法在Tkinter的主循环中更新它们。这样可以确保只有主线程可以修改组件的值,并且可以避免在处理任务时出现GUI不响应或程序崩溃的问题。
Tkinter 中的线程示例
下面是一个更完整的Tkinter应用程序示例,其中包含一个按钮和一个文本框。单击按钮时,会启动一个线程,该线程模拟长时间运行的任务并更新文本框。
import threading
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None,):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Start Long Running Task"
self.hi_there["command"] = self.start_task
self.hi_there.pack(side="top")
self.result_label = tk.Label(self, text="")
self.result_label.pack(side="top")
self.quit = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.quit.pack(side="bottom")
def start_task(self):
t1 = threading.Thread(target=self.long_running_task)
t1.start()
def long_running_task(self):
import time
for i in range(1, 11):
print(i)
time.sleep(1)
self.after(0, lambda: self.result_label.config(text="Task Finished!"))
if __name__ == '__main__':
root = tk.Tk()
app = App(master=root)
app.mainloop()
在这个示例中,我们创建了一个包含一个按钮和一个文本框的简单GUI应用程序。单击按钮时,会启动一个新线程,该线程模拟长时间运行的任务并更新文本框以指示任务完成。
结论
在Tkinter Python中使用线程是一个非常重要的技能,在GUI编程中能够优化应用程序的性能和响应能力。当涉及长时间运行的任务时,使用线程可以使应用程序继续响应并且不会出现崩溃。在使用Tkinter时,确保在主循环内使用 after()
方法以更新和修改Tkinter组件的值,并避免在非主线程中修改它们。