线程与tkinter
我听说在Python中处理线程并不简单,尤其是和tkinter一起使用的时候,更容易出现问题。
我遇到了一个问题。我有两个类,一个是用来做图形界面的(GUI),另一个是用来执行一个无限循环的过程。首先,我启动图形界面的类,然后再启动无限循环的类。我希望当你关闭图形界面的时候,它也能结束那个无限循环的过程,然后整个程序就结束。
下面是一个简化版的代码:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
当我点击右上角的关闭按钮时,控制台会出现以下错误:
Tcl_AsyncDelete: async handler deleted by the wrong thread
我不知道为什么会出现这个错误,也不明白它是什么意思。
2 个回答
0
这里的解决办法很简单,但不容易发现:
在调用 mainwindow.mainloop()
之后,立刻调用 mainWindow.quit()
,这样清理工作就会在创建tk界面的那个线程上进行,而不是在Python退出时的主线程上。
23
所有的Tcl命令都需要来自同一个线程。因为tkinter
依赖于Tcl,所以通常需要确保所有tkinter
的图形界面(GUI)指令都来自同一个线程。问题出在mainWindow
是在tkinterGui
线程中创建的,但由于mainWindow
是tkinterGui
的一个属性,所以它不会被销毁,直到tkinterGui
在主线程中被销毁。
要避免这个问题,可以不把mainWindow
当作tkinterGui
的属性,也就是说,把self.mainWindow
改成mainWindow
。这样一来,mainWindow
就可以在tkinterGui
线程的run
方法结束时被销毁。不过,通常你可以通过使用mainWindow.after
来完全避免使用线程:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
如果你想在一个类里面定义图形界面,还是可以做到的:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
或者更简单的,直接用主线程来运行图形界面的主循环:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()