pythonpython-3.xmultithreadingpython-multithreading

Python 3 - How to terminate a thread instantly?


In my code (a complex GUI application with Tkinter) I have a thread defined in a custom object (a progress bar). It runs a function with a while cicle like this:

def Start(self):
    while self.is_active==True:
        do it..
        time.sleep(1)
        do it..
        time.sleep(1)
    
def Stop(self):
    self.is_active=False

It can terminate only when another piece of code, placed in another thread, changes the attribute self.is_active using the method self.Stop(). I have the same situation in another custom object (a counter) and both of them have to work together when the another thread (the main one) works.

The code works, but I realized that the two threads associated with the progress bar and the counter don't terminate instantly as I wanted, because before to temrinate, they need to wait the end of their functions, and these ones are slow becose of the time.sleep(1) instructions. From the user point of view, it means see the end of the main thread with the progress bar and the cunter that terminate LATE and I don't like it.

To be honest I don't know how to solve this issue. Is there a way to force a thread to terminate instantly without waiting the end of the function?


Solution

  • First off, to be clear, hard-killing a thread is a terrible idea in any language, and Python doesn't support it; if nothing else, the risk of that thread holding a lock which is never unlocked, causing any thread that tries to acquire it to deadlock, is a fatal flaw.

    If you don't care about the thread at all, you can create it with the daemon=True argument, and it will die if all non-daemon threads in the process have exited. But if the thread really should die with proper cleanup (e.g. it might have with statements or the like that manage cleanup of resources outside the process, that won't be cleaned up on process termination), that's not a real solution.

    That said, you can avoid waiting a second or more by switching from using a plain bool and time.sleep to using an Event and using the .wait method on it. This will allow the "sleeps" to be interrupted immediately, at the small expense of requiring you to reverse your condition (because Event.wait only blocks while it's false/unset, so you need the flag to be based on when you should stop, not when you are currently active):

    class Spam:
        def __init__(self):
            self.should_stop = threading.Event()  # Create an unset event on init
        
        def Start(self):
            while not self.should_stop.is_set():
                # do it..
    
                if self.should_stop.wait(1):
                    break
    
                # do it..
    
                if self.should_stop.wait(1):
                    break
    
        def Stop(self):
            self.should_stop.set()
    

    On modern Python (3.1 and higher) the wait method returns True if the event was set (on beginning the wait or because it got set while waiting), and False otherwise, so whenever wait returns True, that means you were told to stop and you can immediately break out of the loop. You also get notified almost immediately, instead of waiting up to one second before you can check the flag.

    This won't cause the real "do it.." code to exit immediately, but from what you said, it sounds like that part of the code isn't all that long, so waiting for it to complete isn't a big hassle.

    If you really want to preserve the is_active attribute for testing whether it's still active, you can define it as a property that reverses the meaning of the Event, e.g.:

     @property
     def is_active(self):
         return not self.should_stop.is_set()