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.
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.
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.
-
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
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.
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.
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.
-
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 ofThread
. -
You should use a
threading.Event
instead of abool
variable andtime.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 usedatetime.datetime.now()
for local time ordatetime.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