7

There is a solution posted here to create a stoppable thread. However, I am having some problems understanding how to implement this solution.

Using the code...

import threading

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

    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop_event = threading.Event()

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

    def stopped(self):
        return self._stop_event.is_set()

How can I create a thread that runs a function that prints "Hello" to the terminal every 1 second. After 5 seconds I use the .stop() to stop the looping function/thread.

Again I am having troubles understanding how to implement this stopping solution, here is what I have so far.

import threading
import time

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

    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop_event = threading.Event()

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

    def stopped(self):
        return self._stop_event.is_set()

def funct():
    while not testthread.stopped():
        time.sleep(1)
        print("Hello")

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

Code above creates the thread testthread which can be stopped by the testthread.stop() command. From what I understand this is just creating an empty thread... Is there a way I can create a thread that runs funct() and the thread will end when I use .stop(). Basically I do not know how to implement the StoppableThread class to run the funct() function as a thread.

Example of a regular threaded function...

import threading
import time

def example():
    x = 0
    while x < 5:
        time.sleep(1)
        print("Hello")
        x = x + 1

t = threading.Thread(target=example)
t.start()
t.join()
#example of a regular threaded function.
FrontDoor
  • 75
  • 1
  • 5
  • You need to do something in your thread in response to the event. For example, check if `self.stopped` after every iteration. The thread will not magically stop running unless you do that. – Mad Physicist Dec 20 '17 at 19:00
  • Did you mean `while not testthread.stopped()`? – Mad Physicist Dec 20 '17 at 19:01
  • You never extend your thread or pass in a task to run. How do you imagine `funct` is going to run? – Mad Physicist Dec 20 '17 at 19:02
  • Can you post an example of how you would get a regular thread, that can't be stopped, to run the `print('Hello')` line every second? I think that will help you think through this. Right now it looks like you need a basic threading tutorial, not help getting deeper into the swamp. – Mad Physicist Dec 20 '17 at 19:04
  • I have cut alot of code for this post, accidentally cut the .start(). I have added an example of a regular threaded function to the post. Basically my question is how can I create an instance of the StoppableThread class that references the funct() function... I want to run funct() in a thread, but then have the option to end that thread with the .stop() command. – FrontDoor Dec 20 '17 at 19:31
  • That is much better. Now you have a question I can answer because I see what your actual problem is. – Mad Physicist Dec 20 '17 at 19:33

3 Answers3

6

There are a couple of problems with how you are using the code in your original example. First of all, you are not passing any constructor arguments to the base constructor. This is a problem because, as you can see in the plain-Thread example, constructor arguments are often necessary. You should rewrite StoppableThread.__init__ as follows:

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

Since you are using Python 3, you do not need to provide arguments to super. Now you can do

testthread = StoppableThread(target=funct)

This is still not an optimal solution, because funct uses an external variable, testthread to stop itself. While this is OK-ish for a tiny example like yours, using global variables like that normally causes a huge maintenance burden and you don't want to do it. A much better solution would be to extend the generic StoppableThread class for your particular task, so you can access self properly:

class MyTask(StoppableThread):
    def run(self):
        while not self.stopped():
            time.sleep(1)
            print("Hello")

testthread = MyTask()
testthread.start()
time.sleep(5)
testthread.stop()

If you absolutely do not want to extend StoppableThread, you can use the current_thread function in your task in preference to reading a global variable:

def funct():
    while not current_thread().stopped():
        time.sleep(1)
        print("Hello")

testthread = StoppableThread(target=funct)
testthread.start()
sleep(5)
testthread.stop()
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
2

I found some implementation of a stoppable thread - and it does not rely that You check if it should continue to run inside the thread - it "injects" an exception into the wrapped function - that will work as long as You dont do something like :

while True: 
    try: 
        do something
    except:
        pass

definitely worth looking at !

see : https://github.com/kata198/func_timeout

maybe I will extend my wrapt_timeout_decorator with such kind of mechanism, which You can find here : https://github.com/bitranox/wrapt_timeout_decorator

bitranox
  • 1,664
  • 13
  • 21
1

Inspired by above solution I created a small library, ants, for this problem.

Example

from ants import worker

@worker
def do_stuff():
    ...
    thread code
    ...

do_stuff.start()
...
do_stuff.stop()

In above example do_stuff will run in a separate thread being called in a while 1: loop

You can also have triggering events , e.g. in above replace do_stuff.start() with do_stuff.start(lambda: time.sleep(5)) and you will have it trigger every 5:th second

The library is very new and work is ongoing on GitHub https://github.com/fa1k3n/ants.git

Johan
  • 1,633
  • 15
  • 22
  • `ImportError: cannot import name 'worker' from 'ants' (C:\Users\MN\AppData\Local\Programs\Python\Python37\lib\site-packages\ants\__init__.py)` – Nouman May 25 '20 at 09:13