Reputation: 4031
I'm running this simple code:
import threading, time
class reqthread(threading.Thread):
def run(self):
for i in range(0, 10):
time.sleep(1)
print('.')
try:
thread = reqthread()
thread.start()
except (KeyboardInterrupt, SystemExit):
print('\n! Received keyboard interrupt, quitting threads.\n')
But when I run it, it prints
$ python prova.py
.
.
^C.
.
.
.
.
.
.
.
Exception KeyboardInterrupt in <module 'threading' from '/usr/lib/python2.6/threading.pyc'> ignored
In fact python thread ignore my Ctrl+C keyboard interrupt and doesn't print Received Keyboard Interrupt
. Why? What is wrong with this code?
Upvotes: 70
Views: 84594
Reputation: 61
I know this is quite old but I had this exact issue and required the Ctrl-C behavior to work on Docker (ubuntu 20.04) and on Windows. On windows specifically, the signal handling is done only onto the main thread, only when the thread is not in a wait state. This is both true for a try: except KeyboardInterrupt: and for a signal.signal(signal.SIGINT, handler) where either gets raised or called only when the main thread is out of a wait.
For instance if you change your code to the following and press Ctrl-C midway, you will see that the exception gets caught but only when reqThread actually terminates and therefore thread.join() returns.
import threading, time
class reqthread(threading.Thread):
def run(self):
for i in range(0, 10):
time.sleep(1)
print('.')
try:
thread = reqthread()
thread.start()
thread.join()
except (KeyboardInterrupt, SystemExit):
print('\n! Received keyboard interrupt, quitting threads.\n')
However, an interesting thing is that when the main thread is running an asyncio loop, it will always catch a Ctrl-C on both Windows and Linux (at least on the docker Ubuntu image I am running).
the following piece of code demonstrates the behavior
import threading, time, signal, asyncio
localLoop = asyncio.new_event_loop()
syncShutdownEvent = threading.Event()
class reqthread(threading.Thread):
def run(self):
for i in range(0, 10):
time.sleep(1)
print('.')
if syncShutdownEvent.is_set():
break
print("reqthread stopped")
done()
return
def done():
localLoop.call_soon_threadsafe(lambda: localLoop.stop())
def handler(signum, frame):
signal.getsignal(signum)
print(f'\n! Received signal {signal.Signals(signum).name}, quitting threads.\n')
syncShutdownEvent.set()
def hookKeyboardInterruptSignals():
for selectSignal in [x for x in signal.valid_signals() if isinstance(x, signal.Signals) and x.name in ('SIGINT', 'SIGBREAK')]:
signal.signal(selectSignal.value, handler)
hookKeyboardInterruptSignals()
thread = reqthread()
thread.start()
asyncio.set_event_loop(localLoop)
localLoop.run_forever()
localLoop.close()
and will give you the same behavior on both Windows and Ubuntu
python scratch_14.py
.
.
! Received keyboard interrupt, quitting threads.
.
reqthread stopped
for my specific application where is need to use 1 thread running synchronous code and 1 thread running async code i actually use a total of three threads.
EDIT: fixed a typo that caused the first code block import statement to be interpreted as plain text instead of part of the code block
Upvotes: 0
Reputation: 1353
Slight modification of Ubuntu's solution.
Removing tread.daemon = True
as suggested by Eric and replacing the sleeping loop by signal.pause():
import signal
try:
thread = reqthread()
thread.start()
signal.pause() # instead of: while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
print('Received keyboard interrupt, quitting threads.)
Upvotes: 9
Reputation: 880797
Try
try:
thread = reqthread()
thread.daemon = True
thread.start()
while True:
time.sleep(100)
except (KeyboardInterrupt, SystemExit):
print('Received keyboard interrupt, quitting threads.')
Without the call to time.sleep
, the main process is jumping out of the try...except
block too early, so the KeyboardInterrupt
is not caught. My first thought was to use thread.join
, but that seems to block the main process (ignoring KeyboardInterrupt) until the thread
is finished.
thread.daemon=True
causes the thread to terminate when the main process ends.
Upvotes: 79
Reputation: 4524
Putting the try ... except
in each thread and also a signal.pause()
in true main()
works for me.
Watch out for import lock though. I am guessing this is why Python doesn't solve ctrl-C by default.
Upvotes: 0
Reputation: 6089
To summarize the changes recommended in the comments, the following works well for me:
try:
thread = reqthread()
thread.start()
while thread.isAlive():
thread.join(1) # not sure if there is an appreciable cost to this.
except (KeyboardInterrupt, SystemExit):
print '\n! Received keyboard interrupt, quitting threads.\n'
sys.exit()
Upvotes: 17
Reputation: 68330
My (hacky) solution is to monkey-patch Thread.join()
like this:
def initThreadJoinHack():
import threading, thread
mainThread = threading.currentThread()
assert isinstance(mainThread, threading._MainThread)
mainThreadId = thread.get_ident()
join_orig = threading.Thread.join
def join_hacked(threadObj, timeout=None):
"""
:type threadObj: threading.Thread
:type timeout: float|None
"""
if timeout is None and thread.get_ident() == mainThreadId:
# This is a HACK for Thread.join() if we are in the main thread.
# In that case, a Thread.join(timeout=None) would hang and even not respond to signals
# because signals will get delivered to other threads and Python would forward
# them for delayed handling to the main thread which hangs.
# See CPython signalmodule.c.
# Currently the best solution I can think of:
while threadObj.isAlive():
join_orig(threadObj, timeout=0.1)
else:
# In all other cases, we can use the original.
join_orig(threadObj, timeout=timeout)
threading.Thread.join = join_hacked
Upvotes: 0