Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch Keyboard Interrupt in program that is waiting on an Event

The following program hangs the terminal such that it ignores Ctrl+C. This is rather annoying since I have to restart the terminal every time one of the threads hang.

Is there any way to catch the KeyboardInterrupt while waiting on an event?

import threading
def main():
    finished_event = threading.Event()
    startThread(finished_event)
    finished_event.wait()#I want to stop the program here
    print('done!')
def startThread(evt):
    """Start a thread that will trigger evt when it is done"""
    #evt.set()
if __name__ == '__main__':
    main()
like image 782
Navin Avatar asked Jan 20 '13 02:01

Navin


People also ask

How does Python detect KeyboardInterrupt?

Interpreter in python checks regularly for any interrupts while executing the program. In python, interpreter throws KeyboardInterrupt exception when the user/programmer presses ctrl – c or del key either accidentally or intentionally.

What signal is KeyboardInterrupt?

In computing, keyboard interrupt may refer to: A special case of signal (computing), a condition (often implemented as an exception) usually generated by the keyboard in the text user interface. A hardware interrupt generated when a key is pressed or released, see keyboard controller (computing)

How do you make a KeyboardInterrupt in Python?

In Python, there is no special syntax for the KeyboardInterrupt exception; it is handled in the usual try and except block. The code that potentially causes the problem is written inside the try block, and the 'raise' keyword is used to raise the exception, or the python interpreter raises it automatically.


4 Answers

If you want to avoid polling, you can use the pause() function of the signal module instead of finished_event.wait(). signal.pause() is a blocking function and gets unblocked when a signal is received by the process. In this case, when ^C is pressed, SIGINT signal unblocks the function. Note that the function does not work on Windows according to the documentation. I've tried it on Linux and it worked for me.

I came across this solution in this SO thread.

like image 160
GingerNinja23 Avatar answered Oct 05 '22 14:10

GingerNinja23


Update: On the current Python 3 finished_event.wait() works on my Ubuntu machine (starting with Python 3.2). You don't need to specify the timeout parameter, to interrupt it using Ctrl+C. You need to pass the timeout parameter on CPython 2.

Here's a complete code example:

#!/usr/bin/env python3
import threading

def f(event):
    while True:
        pass
    # never reached, otherwise event.set() would be here

event = threading.Event()
threading.Thread(target=f, args=[event], daemon=True).start()
try:
    print('Press Ctrl+C to exit')
    event.wait()
except KeyboardInterrupt:
    print('got Ctrl+C')

There could be bugs related to Ctrl+C. Test whether it works in your environment.


Old polling answer:

You could try to allow the interpreter to run the main thread:

while not finished_event.wait(.1): # timeout in seconds
    pass

If you just want to wait until the child thread is done:

while thread.is_alive():
    thread.join(.1)
like image 32
jfs Avatar answered Oct 05 '22 15:10

jfs


You could also patch the Event.wait() function in the following manner:

def InterruptableEvent():
    e = threading.Event()

    def patched_wait():
        while not e.is_set():
            e._wait(3)

    e._wait = e.wait
    e.wait = patched_wait
    return e


>>> event = InterruptableEvent()
>>> try:
...     event.wait()
... except KeyboardInterrupt:
...     print "Received KeyboardInterrupt"
... 
^CReceived KeyboardInterrupt

This works because wait() with a timeout argument will raise a KeyboardInterrupt.

like image 22
Pieter van den Ham Avatar answered Oct 05 '22 16:10

Pieter van den Ham


Based on @Pete's answer, but with subclassing and using the actual Event.wait method, just with smaller timeouts to allow handling of KeyboardInterrupts and such in between:

class InterruptableEvent(threading.Event):
    def wait(self, timeout=None):
        wait = super().wait  # get once, use often
        if timeout is None:            
            while not wait(0.01):  pass
        else:
            wait(timeout)
like image 42
Jeronimo Avatar answered Oct 05 '22 16:10

Jeronimo