无法解决多线程中的Tcl_Asyncdelete问题

时间:2016-08-06 03:15:36

标签: python multithreading

我是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()

1 个答案:

答案 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崩溃。