Python Threading not stopping

Question:

first try at threading in python, the threading is working after following a post on Stack however I can’t stop the thread with ctrl-c despite having a terminate method in my class called with a keyboard interrupt.

Basically I’m polling a temp sensor in the background and using a thread to do that whilst the main body of the code is watching GPIO inputs, everything is working fine, its just threading thats baffling me/

(there’s intention overkill re print messages so I can understand where its going wrong etc)

class GetClimate(threading.Thread):
  def __init__(self):
    self._running = True
    print "Data Collection STARTED"
  def terminate(self):
    self._running = False
    print "Data Collection ENDED"
  def run(self):
    while self._running is True:
      ts = time.time()
      global GARAGE_HUM, GARAGE_TEMP
      timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
      GARAGE_HUM, GARAGE_TEMP = Adafruit_DHT.read_retry(Adafruit_DHT.DHT22, 4)
      if GARAGE_HUM is not None and GARAGE_TEMP is not None:
        print timestamp + " - Temperature: %.1f c" % GARAGE_TEMP
        print timestamp + " - Humidity:    %.1f %%" % GARAGE_HUM
        time.sleep(5) # seconds between sample

#---------Start Getting Climate data------------

def StartClimate():
    c = GetClimate()
    t = threading.Thread(target=c.run)
    t.start()
    print "Started Climate"
#--------Stop Climate Data----------------------
def StopClimate():
    print "Called Climate stop .."
    c = GetClimate()
    c.terminate()
    print "Stopped Climate"

When ctrl-c is used its calling the StopClimate() function OK as it outputs:

Data Collection STARTED
Started Climate
..Ready
2016-01-09 20:48:45 - Temperature: 24.8 c
2016-01-09 20:48:45 - Humidity:    51.9 %
Shutdown requested...exiting
Called Climate stop ..
Data Collection STARTED
Data Collection ENDED
Stopped Climate
^C2016-01-09 20:48:51 - Temperature: 24.8 c
2016-01-09 20:48:51 - Humidity:    51.9 %
2016-01-09 20:48:57 - Temperature: 24.7 c
2016-01-09 20:48:57 - Humidity:    51.8 %

…. as you can see the thread is still polling.

Asked By: northwarks

||

Answers:

In StopClimate, you instantiate a new thread – hence the STARTED message – and terminate it immediately, without ever calling its run method. But the original thread, which you instantiated in StartClimate, is not affected in any way. It’s that thread, which you stored in t, that you need to terminate.

Answered By: Daniel Roseman

Your calls ‘c = GetClimate()’ in StartClimate and StopClimate each create a GetClimate instance. So the GetClimate you terminate in StopClimate is not the GetClimate you started in StartClimate.

Also, the way your code is constructed, there’s no need to make GetClimate a subclass of threading.Thread as you’re calling the GetClimate run() method in a separate thread (the t=threading.Thread(target=c.run() line.)

I suggest you have StartClimate() return c, that you store that result somewhere and then pass c in as an argument to StopClimate(). That’s still not the best way to structure all this, but it’s the minimal change to your code.

Answered By: Pat Knight
  • You create new instances each time you call your functions.
    That won’t stop the existing threads.

  • You do not use your own class, just the run method as you give it to a new instance of Thread.

  • You should use a threading.Event instead of a bool variable and time.sleep

This is a basic PeriodicThread, which calls its main method every interval seconds:

from threading import Thread, Event
from time import sleep

class PeriodicThread(Thread):

    def __init__(self, interval):
        self.stop_event = Event()
        self.interval = interval
        super(PeriodicThread, self).__init__()
    
    def run(self):
         while not self.stop_event.is_set():
             self.main()
             # wait self.interval seconds or until the stop_event is set
             self.stop_event.wait(self.interval)        
   
    def terminate(self):
        self.stop_event.set()
    
    def main(self):
       print('Hello World')

And you would use it like this:

if __name__ == '__main__':
    # the workers main function is called and then 5 seconds sleep
    worker = PeriodicThread(interval=5)
    worker.start()
          
    try:
       # this is to keep the main thread alive
       while True:
           sleep(1)
    except (KeyboardInterrupt, SystemExit):
        worker.terminate()

some minor comments

  • If you are using datetime, you do not need to call time.time, you can just use datetime.datetime.now() for local time or datetime.datetime.now(timezone.utc) for utc time.

  • I would not use global variables, make them members of you class which you update.

So using this, your code would become:

from datetime import datetime, timezone

class GetClimate(PeriodicThread):
    def main(self):
       timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
       temperature, humidity = Adafruit_DHT.read_retry(Adafruit_DHT.DHT22, 4)
       if temperature is not None and humidity is not None:
           print(timestamp + ' - Temperature: {:.1f} C'.format(temperature))
           print(timestamp + ' - Humidity:    {:.1f} %'.format(humidity))
           self.temperature = temperature
           self.humidity = humidity
Answered By: MaxNoe
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.