Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't Solve Tcl_Asyncdelete in Multithreading

I am pretty new to programming in Python (3.x), so I recently decided to tackle my first real project and attempted to make a really simple client/server chat program. However, after I terminate the tkinter windows on either the client or server ends, an error always shows up: Tcl_AsyncDelete: async handler deleted by the wrong thread.

I have searched and found threads on this, but I still don't understand what is wrong because I feel like the other answers I have found don't really fit the context of my application. As far as I understand, this error is caused by terminating a tkinter window outside of the thread it was created, but I don't see how that is happening. Please help, thanks in advance!

SERVER CODE:

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

CLIENT CODE:

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()
like image 522
ajflj Avatar asked Feb 20 '26 16:02

ajflj


1 Answers

I solved this problem in my code by triggering a garbage collect.

Here is one way this problem can happen, even if you don't call tkinter code from a thread. If you delete a tkinter window, or widget, then Python will eventually call the delete method for the widgets. The problem occurs when Python's garbage collect decides to release these objects while the thread is running. In other words, the garbage collect runs in the thread's context instead of the main thread.

The fix:

When you delete a tkinter Widget, like a button, trigger a garbage collect. "Delete" can simply be that you re-use a variable name that used to hold a tkinter widget.

Find the location in your code that you're freeing up the tkinter resource (deleting the widget) and call Python's garbage collect to trigger a collection to run immediately.

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

This completely fixed the random aync delee crashes I was having when running tkinter in a multi-threaded environment.

like image 150
Mike from PSG Avatar answered Feb 27 '26 08:02

Mike from PSG



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!