Kurt Peek Kurt Peek - 1 month ago 15
Python Question

How to make a stoppable thread with a target function that is an infinite loop

Suppose I would like to run a function, called

run_forever()
, in a thread, but still have it 'stoppable' by pressing Cntrl+C. I've seen ways of doing this using a
StoppableThread
subclass of
threading.Thread
, but these seem to involve 'copying' the target function into that subclass. I would like to instead keep the function 'where it is'.

Consider the following example:

import time
import threading

def run_forever(): # An externally defined function which runs indefinitely
while True:
print("Hello, world!")
time.sleep(1)

class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""

def __init__(self, *args, **kwargs):
super(StoppableThread, self).__init__(*args, **kwargs)
self._stop = threading.Event()

def stop(self):
self._stop.set()

def stopped(self):
return self._stop.isSet()

def run(self):
while not self.stopped():
run_forever() # This doesn't work
# print("Hello, world!") # This does
self._stop.wait(1)

thread = StoppableThread()
thread.start()
time.sleep(5)
thread.stop()


The target function
run_forever
is itself a while-loop which never exits. However, to get the desired behavior the
wait()
command has to be inside that while-loop, as I understand it.

Is there any way of achieving the desired behavior without modifying the
run_forever()
function?

Answer

I doubt it's possible.
BTW, have you tried the second solution with ThreadWithExc from the post you linked earlier?
It works if the loop is busy pure Python(eg no sleep), otherwise I'd switch to multiprocessing and kill subprocess. Here is the code that hopefully exits gracefully(*nix only):

from multiprocessing import Process
from signal import signal, SIGTERM
import time

def on_sigterm(*va):
    raise SystemExit

def fun():
    signal(SIGTERM, on_sigterm)
    try:
        for i in xrange(5):
            print 'tick', i
            time.sleep(1)
    finally:
        print 'graceful cleanup'

if __name__=='__main__':
    proc = Process(target=fun)
    proc.start()
    time.sleep(2.5)
    proc.terminate()
    proc.join()