Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 Ctrl+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?

like image 969
Kurt Peek Avatar asked Oct 18 '22 23:10

Kurt Peek


1 Answers

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()
like image 98
robyschek Avatar answered Oct 21 '22 06:10

robyschek