2

I have a Python application with a Tkinter GUI. In the app, the user invokes a long-running background process (implemented as a Thread from Python's threading module). I'm having trouble killing the background thread if I quit the program before it's complete. My code works if I quit the application by closing the root window via the 'X' at its top corner, but not if I quit from the top-level menu bar (i.e. Python > Quit or Ctrl+Q). Since most applications use the latter, I'd really like to make that work.

Right now, I kill the background thread with code that looks like this:

class BackgroundCallFrame(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.background_call = BackgroundCall()
        self.background_call.start()

class BackgroundCall(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self._stop_req = threading.Event()

    def run(self):
        for i in range(1,100000):
            if self._stop_req.is_set():
                return
            else:
                # do stuff

    def stop(self):
        self._stop_req.set();

def main():

    def kill_all_threads():
        if child.background_call is not None:
           child.background_call.stop()
           child.background_call.join()
        root.destroy()

    root = Tk()
    root.wm_protocol ("WM_DELETE_WINDOW", kill_all_threads)

    child = BackgroundCallFrame()
    child.pack()
    root.mainloop()

if __name__ == '__main__':
    main()

How can I make this work if I quit the program without explicitly closing the root window first?

I believe my problem is that kill_all_threads() is not called when I invoke Ctrl+Q, because any print statements I add to it never appear in the console.

Katrina
  • 409
  • 5
  • 16

1 Answers1

0

Usually you will want to kill a thread by setting a variable in that thread to signal it to stop working. In the run method of your thread you will need to periodically check that variable to know when to exit the run method. Here's an example...

import threading
import time

class WorkerThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.do_stop = False

    def run(self):
        while True:
            time.sleep(.5)

            print 'I am running'

            if self.do_stop:
                return # exit loop and function thus exiting thread

t = WorkerThread()
t.start()

time.sleep(4) # sleep while our thread runs and prints to the command line

print 'setting do_stop to True'
t.do_stop = True # signal to thread to stop

t.join() # wait for thread to exit

print 'all done'

...outputs...

I am running
I am running
I am running
I am running
I am running
I am running
I am running
setting do_stop to True
I am running
all done
Genome
  • 1,106
  • 8
  • 10
  • Sorry for the lack of clarification - in `child.background_call.stop()` I ask my thread to stop using `t._stop_req.set()`, as described in the first answer to this SO question: http://stackoverflow.com/questions/11938109/how-do-i-stop-a-python-process-instantly-from-a-tkinter-window. I'm not sure that this method would do differently. My method works most of the time, just not when quitting from the top-level menu bar. – Katrina May 09 '14 at 12:54
  • That answer you linked does not look correct to me. Most importantly because the thread is started with .run()...it should be started with .start(). That is probably the core issue you have. In addition Threading.Event is used for communicating between threads not signaling exits. That linked answer also will not work because it contains errors in the method definitions of MyThread (they don't reference self). – Genome May 09 '14 at 14:48
  • Thanks so much for following up. I didn't mean that my entire code looked like that in the linked answer, only that I also used an `Event` to notify my background thread to exit. This might be overkill, but I am pretty sure it's not my problem, because the background thread does die when the root window is closed. Rather, I think the issue is that my `kill_all_threads` callback is not called when I quit the program. – Katrina May 09 '14 at 18:30
  • I should have asked a different, much simpler question - I've posted it here: http://stackoverflow.com/questions/23571888/tkinter-wm-delete-window-callback-does-not-run-on-application-quit. Thanks again. – Katrina May 09 '14 at 18:48