Reputation: 2576
I have a script which uses threads, but it is unable to catch Ctrl + C.
Here it is the sample code to reproduce this error:
import threading
import time
import signal
class DummyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._running = True
signal.signal(signal.SIGINT, self.stop)
signal.signal(signal.SIGTERM, self.stop)
def stop(self, signum=None, frame=None):
self._running = False
def run(self):
while self._running:
time.sleep(1)
print("Running")
if __name__ == "__main__":
try:
t = DummyThread()
t.start()
while True:
print("Main thread running")
time.sleep(0.5)
except KeyboardInterrupt:
print("This never gets printed")
t.stop()
finally:
print("Exit")
When I run python3 script.py
it starts running, but it does not catch ctrl+c. I have googled it but I have not found a solution. I have to kill the script with SIGTERM, but I want DummyThread
to stop gracefully.
Upvotes: 1
Views: 4827
Reputation: 23114
class DummyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self._running = True signal.signal(signal.SIGINT, self.stop) signal.signal(signal.SIGTERM, self.stop)
The program actually does not work as expected because of those last two lines and would work without them.
The reason is that, if you press Ctrl-C, the SIGINT
signal is handled by the signal handler that is set up by signal.signal
and self.stop
is called. So the thread should actually stop.
But in the main thread, the while True
loop is still running. Since the signal has already been handled, there will be no KeyboardInterrupt
exception raised by the Python runtime. Therefore you never get to the except
part.
if __name__ == "__main__": try: t = DummyThread() t.start() while True: # you are stuck in this loop print("Main thread running") time.sleep(0.5) except KeyboardInterrupt: # this never happens print("This never gets printed") t.stop()
Only one signal handler should be set up to call the stop
method. So there are two options to solve the problem:
Handle the signal implicitly by catching the KeyboardInterrupt
exception. This is achieved by simply removing the two signal.signal(...)
lines.
Set up an explicit signal handler (as you did by using signal.signal
in DummyThread.__init__
), but remove the while True:
loop from the main thread and do not try to handle KeyboardInterrupt
. Instead, just wait for the DummyThread
to finish on its own by using its join
method:
if __name__ == "__main__":
t = DummyThread()
t.start()
t.join()
print("Exit")
Upvotes: 3
Reputation: 3826
The main point is that you can't work with signals in any other Thread except the Main Thread. The Main Thread is the only one which can receive signals and handle them. I can offer the following solution, it is based on Event
sync primitive.
According to Python documantation:
Signals and threads Python signal handlers are always executed in the main Python thread, even if the signal was received in another thread. This means that signals can’t be used as a means of inter-thread communication. You can use the synchronization primitives from the threading module instead.
Besides, only the main thread is allowed to set a new signal handler.
from threading import Thread, Event
import time
class DummyThread(Thread):
def __init__(self, event: Event):
Thread.__init__(self)
self.stop_event = event
def run(self):
# we are monitoring the event in the Main Thread
while not self.stop_event.is_set():
time.sleep(1)
print("Running")
# only Main Thread can make the point reachable
print("I am done !")
if __name__ == "__main__":
try:
e = Event()
t = DummyThread(e)
t.start()
while True:
print("Main thread running")
time.sleep(0.5)
except KeyboardInterrupt:
e.set()
finally:
print("Exit")
Another possible choice is to use daemon
Thread for such tasks like in your code example (when you just printing smth in the screen every second, rather than e.g. close database connection or some similar task). If main thread is stoped the daemon
Thread will stop too.
Upvotes: 2
Reputation: 940
As shown in your code, you used KeyboardInterrupt
to call stop()
function. See how Listener does the same task and stops the execution which was unable to catch from Ctrl + C. You dont have to kill the script with SIGTERM anymore
import threading
import time
import signal
import os
from pynput.keyboard import Key, Listener
class DummyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._running = True
signal.signal(signal.SIGINT, self.stop)
signal.signal(signal.SIGTERM, self.stop)
def stop(self, signum=None, frame=None):
self._running = False
print ("Bye Bye . .")
os._exit(1)
def run(self):
while self._running:
time.sleep(1)
print("Running")
if __name__ == "__main__":
t = DummyThread()
def func2():
try:
t.start()
while True:
print("Main thread running")
time.sleep(0.5)
except KeyboardInterrupt:
print("No need for this")
t.stop()
finally:
print("Exit")
def func1():
with Listener(on_press = t.stop) as listener :
listener.join()
threading.Thread(target=func1).start()
threading.Thread(target=func2).start()
Upvotes: 0