3

I am writing this software for a project homework, but I am having trouble with mixing threads and tkinter. The following piece works mostly as expected, but when I close it (after starting it in the Python shell), windows shows an error: "Python stopped working".

import threading
import time
import tkinter
import tkinter.ttk

class BTClient:
    def __init__(self, master):
        self.root = master
        self.root.bind("<Destroy>", self.on_destroy)

        self.t = threading.Thread(target=self.update)
        self.running = False

    def on_destroy(self, event):
        self.running = False

    def run_thread(self):
        self.running = True
        self.t.start()

    def update(self):
        while self.running:
            print("Update.")
            time.sleep(1)

def main(args):
    root = tkinter.Tk()
    client = BTClient(root)
    client.run_thread()
    root.mainloop()

if __name__ == "__main__":
    import sys
    main(sys.argv)

How can I solve that problem? Is it caused by the design I am using? Should I change it?

Edit 1: When I remove the self.root declaration in __init__ and only use the master reference the problem is solved, but I need to have references to the GUI objects, first to build the GUI and also to get input from them, so I don't know how to solve that. Maybe passing objects as arguments to everything that may need them?

sempiedram
  • 137
  • 12
  • have you tried to to encapsulate the thread in try/except to see what the error message is? I don't have tkinter experience, but similar problems arise in wxpython because the thread tries to access the window after its closed. – user2682863 Oct 31 '15 at 00:55
  • maybe try setting the thread to daemon self.t.setDaemon(True) – user2682863 Oct 31 '15 at 00:56
  • If I set it as daemon, the thread dies immediately after the main thread, right?, without any cleanup, which I think I need to do. – sempiedram Oct 31 '15 at 01:05
  • Yeah. Try moving the threaded method outside of the BTClient class, and add a try/except clause – user2682863 Oct 31 '15 at 01:08
  • 1
    I'm assuming that when the window closes, it deletes the BTClient instance and your thread still needs that instance (self.running) so its throwing an error – user2682863 Oct 31 '15 at 01:12
  • I ran the program in the Windows cmd and got the error "Tcl_AsyncDelete: async handler deleted by the wrong thread" – sempiedram Oct 31 '15 at 01:24
  • looks like a common problem on SO, http://stackoverflow.com/questions/26703502/threads-and-tkinter-python-3 – user2682863 Oct 31 '15 at 01:27

2 Answers2

1

Its hard to know exactly whats happening without seeing your actual code, but I would make your threaded method a function outside of the BTClient and also move the thread outside of the client and add a call to thread.join() after the mainloop to have your thread finish its cleanup before the BTClient is deleted by the GC

def update():
    import time
    while thread._keep_alive:
        time.sleep(1)
        print("thread running")

thread = threading.Thread(target=update)
thread._keep_alive = True
thread.start()

at the end

thread._keep_alive = False
thread.join()
user2682863
  • 3,097
  • 1
  • 24
  • 38
0

So I added the line client.t.join() after root.mainloop() and that solves the problem even though I don't really know why. I think it's because python was destroying the tkinter objects when there were still references to them in other threads, so waiting for them to close solves it. Thanks.

sempiedram
  • 137
  • 12
  • 1
    yeah that's why. The join method prevents the client from being deleted from existence until the thread completes. – user2682863 Oct 31 '15 at 02:03