我是Python(3.x)编程的新手,所以我最近决定解决我的第一个真正的项目,并尝试制作一个非常简单的客户端/服务器聊天程序。但是,在我终止客户端或服务器端的tkinter窗口后,总会出现一个错误:Tcl_AsyncDelete:由错误的线程删除的异步处理程序。
我已经搜索过并发现了这方面的线索,但我仍然不明白什么是错的,因为我觉得我发现的其他答案并不适合我申请的背景。据我所知,这个错误是由于在创建它的线程之外终止一个tkinter窗口引起的,但我不知道这是怎么回事。请帮助,提前谢谢!
import socket
from tkinter import *
from tkinter.messagebox import *
from threading import *
import tkinter.scrolledtext as tkst
import sys
def main():
global c
q = socket.socket()
q.bind(("", 0))
open_port = (q.getsockname()[1])
q.close()
menu = Tk()
menu.configure(bg="black")
menu.iconbitmap("chat.ico")
menu.title("chat")
Label(fg="white", bg="black", text="Set host IP:", font=("Comic Sans", 14)).grid(row=0, column=0, padx=10, pady=10)
Label(fg="white", bg="black", text="Set host port:", font=("Comic Sans", 14)).grid(row=1, column=0, padx=10, pady=10)
Label(fg="white", bg="black", text="Username:", font=("Comic Sans", 14)).grid(row=2, column=0, padx=10, pady=10)
ip_enter = Entry(width=16, font=("Comic Sans", 14))
ip_enter.insert(END, socket.gethostbyname(socket.gethostname()))
port_enter = Entry(width=16, font=("Comic Sans", 14))
port_enter.insert(END, open_port)
user_enter = Entry(width=16, font=("Comic Sans", 14))
ip_enter.grid(row=0, column=1, padx=10, pady=10)
port_enter.grid(row=1, column=1, padx=10, pady=10)
user_enter.grid(row=2, column=1, padx=10, pady=10)
def startup():
try:
host = ip_enter.get()
port = port_enter.get()
username = user_enter.get()
if username == "":
raise ValueError("username cannot be empty")
s = socket.socket()
s.bind((host, int(port)))
menu.destroy()
chat = Tk()
chat.configure(bg="black")
chat.iconbitmap("chat.ico")
chat.title("chat")
def closeout():
s.close()
chat.destroy()
chat.protocol("WM_DELETE_WINDOW", closeout)
chatbox = tkst.ScrolledText(state=DISABLED)
chatbox.grid(row=0, column=0, columnspan=2, padx=2, pady=10)
message = Entry(width=80, font=("Comic Sans", 10))
message.grid(row=1, column=0, padx=10, pady=10)
def sync():
s.listen(1)
global c
c, addr = s.accept()
c.send(("Connected to host " + username + "\n").encode("UTF-8"))
data = (c.recv(1024)).decode("UTF-8")
chatbox.config(state=NORMAL)
chatbox.insert(END, (data))
chatbox.config(state=DISABLED)
chatbox.see("end")
def rec():
while True:
try:
data = (c.recv(1024)).decode("UTF-8")
if data:
chatbox.config(state=NORMAL)
chatbox.insert(END, (data))
chatbox.config(state=DISABLED)
chatbox.see("end")
else:
break
except:
break
rec = Thread(target=rec)
rec.start()
t2 = Thread(target=sync)
t2.start()
def senddata(event):
try:
chatbox.config(state=NORMAL)
if message.get() != "":
mesg = ("<" + username + "> " + message.get() + "\n")
message.delete(0, "end")
chatbox.insert(END, mesg)
chatbox.config(state=DISABLED)
c.send(mesg.encode("UTF-8"))
chatbox.see("end")
except OSError:
useless = 1
send = Button(bg="white", text="Send", font=("Comic Sans", 14))
send.bind("<Button-1>", senddata)
send.grid(row=1, column=1, padx=10, pady=10)
chat.bind("<Return>", senddata)
chat.mainloop()
except OSError:
showerror("Error", "Unable to bind to given IP or Port")
except ValueError:
showerror("Error", "Username field cannot be empty")
connect = Button(bg="white", text="Start", font=("Comic Sans", 14), command=startup)
connect.grid(row=3, column=1, padx=10, pady=10)
menu.mainloop()
if __name__ == '__main__':
main()
import socket
from tkinter import *
from tkinter.messagebox import *
from threading import *
import tkinter.scrolledtext as tkst
def main():
menu = Tk()
menu.configure(bg="black")
menu.iconbitmap("chat.ico")
menu.title("chat")
Label(fg="white", bg="black", text="Host IP:", font=("Comic Sans", 14)).grid(row=0, column=0, padx=10, pady=10)
Label(fg="white", bg="black", text="Host port:", font=("Comic Sans", 14)).grid(row=1, column=0, padx=10, pady=10)
Label(fg="white", bg="black", text="Username:", font=("Comic Sans", 14)).grid(row=2, column=0, padx=10, pady=10)
ip_enter = Entry(width=16, font=("Comic Sans", 14))
port_enter = Entry(width=16, font=("Comic Sans", 14))
user_enter = Entry(width=16, font=("Comic Sans", 14))
ip_enter.grid(row=0, column=1, padx=10, pady=10)
port_enter.grid(row=1, column=1, padx=10, pady=10)
user_enter.grid(row=2, column=1, padx=10, pady=10)
def startup():
try:
host = ip_enter.get()
port = port_enter.get()
username = user_enter.get()
if username == "":
raise ValueError("username cannot be empty")
s = socket.socket()
menu.destroy()
chat = Tk()
chat.configure(bg="black")
chat.iconbitmap("chat.ico")
chat.title("chat")
def closeout():
chat.destroy()
s.close()
chat.protocol("WM_DELETE_WINDOW", closeout)
chatbox = tkst.ScrolledText(state=DISABLED)
chatbox.grid(row=0, column=0, columnspan=2, padx=2, pady=10)
message = Entry(width=80, font=("Comic Sans", 10))
message.grid(row=1, column=0, padx=10, pady=10)
def sync():
s.connect((host, int(port)))
s.send((username + " has connected\n").encode("UTF-8"))
data = (s.recv(1024)).decode("UTF-8")
chatbox.config(state=NORMAL)
chatbox.insert(END, (data))
chatbox.config(state=DISABLED)
chatbox.see("end")
t2 = Thread(target=sync)
t2.start()
def senddata(event):
try:
chatbox.config(state=NORMAL)
if message.get() != "":
mesg = ("<" + username + "> " + message.get() + "\n")
message.delete(0, "end")
chatbox.insert(END, mesg)
chatbox.config(state=DISABLED)
s.send(mesg.encode("UTF-8"))
chatbox.see("end")
except OSError:
useless = 1
send = Button(bg="white", text="Send", font=("Comic Sans", 14))
send.bind("<Button-1>", senddata)
send.grid(row=1, column=1, padx=10, pady=10)
chat.bind("<Return>", senddata)
def rec():
while True:
try:
data = (s.recv(1024)).decode("UTF-8")
if data:
chatbox.config(state=NORMAL)
chatbox.insert(END, (data))
chatbox.config(state=DISABLED)
chatbox.see("end")
else:
break
except:
break
rec = Thread(target=rec)
rec.start()
chat.mainloop()
except OSError:
showerror("Error", "Invalid IP or Port")
except ValueError:
showerror("Error", "Username field cannot be empty")
connect = Button(bg="white", text="Connect", font=("Comic Sans", 14), command=startup)
connect.grid(row=3, column=1, padx=10, pady=10)
menu.mainloop()
if __name__ == '__main__':
main()
答案 0 :(得分:0)
我通过触发垃圾回收在代码中解决了这个问题。
即使您不从线程调用tkinter代码,这也是发生此问题的一种方法。如果删除tkinter窗口或窗口小部件,则Python最终将调用窗口小部件的delete方法。当Python的垃圾回收决定在线程运行时释放这些对象时,就会发生问题。换句话说,垃圾回收在线程的上下文中运行,而不是在主线程中运行。
解决方法:
当您删除tkinter窗口小部件(如按钮)时,将触发垃圾回收。 “删除”可以简单地是,您重新使用用于保存tkinter小部件的变量名。
在代码中找到要释放tkinter资源(删除小部件)的位置,然后调用Python的垃圾回收以触发收集立即运行。
import gc
.....
connect = tk.Button(.....)
....
#later when you're done with the button
connect = None # make sure the reference count to the tk.Button is zero
gc.collect() # force Python's garbage collect to run
此 完全修复 ,这是我在多线程环境中运行tkinter时发生的随机aync delee崩溃。