60

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?

Aran-Fey
  • 30,995
  • 8
  • 80
  • 121
Emilio
  • 3,711
  • 9
  • 40
  • 49

5 Answers5

69

Try

try:
  thread=reqthread()
  thread.daemon=True
  thread.start()
  while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
  print '\n! Received keyboard interrupt, quitting threads.\n'

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.

unutbu
  • 711,858
  • 148
  • 1,594
  • 1,547
  • 6
    I believe a timeout on `join`, i.e. `while thread.isAlive: thread.join(5)` will also work to keep the main thread responsive to exceptions. – Dr. Jan-Philip Gehrcke Sep 11 '12 at 19:51
  • 13
    `thread.daemon = True` is actually not recommended because it doesn't allow the thread to clean up any resources left behind... – Erik Kaplun Sep 29 '13 at 14:47
12

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()
Community
  • 1
  • 1
rattray
  • 3,326
  • 26
  • 22
  • Is calling "thread.join()" over and over in the "while thread.isAlive():" a good thing / does that matter? – DevPlayer Sep 15 '16 at 13:39
  • Personally I don't know; might be worth trying to craft a benchmark if perf matters to you for that? – rattray Sep 16 '16 at 00:43
  • 1
    Be aware the exit() and sys.exit() are not the same. It is recommended to use sys.exit(). – DevPlayer Nov 18 '16 at 01:23
  • Thanks @DevPlayer ! Updated to reflect that. For the curious, see http://stackoverflow.com/a/19747562/1048433 for an explanation – rattray Nov 18 '16 at 23:07
5

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 '\n! Received keyboard interrupt, quitting threads.\n'
yaccob
  • 871
  • 9
  • 14
0

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
Albert
  • 57,395
  • 54
  • 209
  • 347
0

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.

personal_cloud
  • 2,846
  • 1
  • 19
  • 25