88

I need to break from time.sleep() using ctrl c.

While 1:
    time.sleep(60)

In the above code when the control enters time.sleep function an entire 60 seconds needs to elapsed for python to handled the CTRL C

Is there any elegant way to do it. such that I can interrupt even when the control is in time.sleep function

edit

I was testing it on a legacy implementation which uses python 2.2 on windows 2000 which caused all the trouble . If I had used a higher version of python CTRL C would have interrupted the sleep() . I did a quick hack by calling sleep(1) inside a for loop . which temporarily fixed my issue

Anuj
  • 9,222
  • 8
  • 33
  • 30
  • No the code was meant to do some monitoring. it monitors every 60 seconds so in order to exit the pgm the user presses CTRL C but he will have to wait 60 seconds to get the prompt back. – Anuj Feb 25 '11 at 06:41
  • 1
    In most systems, Ctrl-C would interrupt the sleep. It certainly does on Unix and on Mac. Which system are you on? – DS. Feb 25 '11 at 07:23
  • 1
    for i in range(60): sleep(1) its better i think – Mohammad Efazati Mar 01 '11 at 10:45

7 Answers7

193

The correct answer is to use python stdlib's threading.Event

Sure you can tune down your sleep interval so you sleep for very short periods, but what if you actually want to run your loop once every 60s? Then you need to do more work to determine if it's time to run or just keep sleeping. Furthermore, you're still technically blocking but for only a short period of time. Contrast to threading.Event:

from threading import Event

exit = Event()

def main():
    while not exit.is_set():
      do_my_thing()
      exit.wait(60)

    print("All done!")
    # perform any cleanup here

def quit(signo, _frame):
    print("Interrupted by %d, shutting down" % signo)
    exit.set()

if __name__ == '__main__':

    import signal
    for sig in ('TERM', 'HUP', 'INT'):
        signal.signal(getattr(signal, 'SIG'+sig), quit);

    main()

When the signal handler calls exit.set(), the main thread's wait() call will immediately be interrupted.

Now, you could use an Event to signal that there's more work to do, etc. But in this case it does double duty as a convenient indicator that we want to quit (e.g. the while not exit.is_set() part.)

You also have the option to put any cleanup code after your while loop.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
thom_nic
  • 7,809
  • 6
  • 42
  • 43
  • 9
    Note: After calling `exit.set()`, the `exit.wait()` call will never work anymore for the event object `exit`, unless you call `exit.clear()`, reference: [threading::Event Objects](https://docs.python.org/2/library/threading.html#event-objects) – Evandro Coan Dec 22 '17 at 19:45
  • 1
    I think this calls `do_my_thing()` every 60 + time the single `do_my_thing()` executions takes. So if `do_my_thing()` always takes 5 seconds, it will execute it once every 65 seconds. – desowin Mar 09 '18 at 07:17
  • @desowin Correct... To make it fire every 60 seconds (approximately), you should set a variable to the first time it fires `start_time = time.time()`, put a counter in the loop `cycle_num += 1`, and do `wait(max(0, start_time+60*cycle_num-time.time()))` instead of just 60. The `max` part is just to avoid a negative wait time. Of course, you should make that 60 a variable parameter too; oh, and don't call a function `exit`! – travc Mar 17 '18 at 00:29
  • 4
    @travc Also it should be noted that time.monotonic() should be preferred over time.time(). The use of time.time() breaks timing when system clock is adjusted/changed. – desowin Mar 21 '18 at 09:24
  • In python3 it's exit.wait() instead of exit.Wait(). – John Jiang Sep 05 '18 at 21:02
  • what if `do_my_thing()` is a blocking function such as `socket.recv()` socket.accept()` or others? – roschach Jan 06 '19 at 14:42
  • @francesco: of course if your loop preforms other blocking operations, they may hang the thread as well. But if you could block on a `recv()` call (with a timeout!) You probably wouldn't also need to sleep/ wait inside your loop as well. The point is, `wait()` does not hang the loop arbitrarily without ability to interrupt if you decide you have work to do or just want to exit as my example demonstrates. – thom_nic Jan 06 '19 at 18:30
  • Is calling synchronization primitives like `Event.set()` safe inside a signal handler? What if the main thread is inside the `Event.is_set()` method when the signal comes in? What if another thread (not the main thread that handles signals) is also wait()ing on the `exit` event? – Mark Rajcok Jul 27 '20 at 17:25
  • 1
    I wanted this to work, but it still waits 60s after ctrl-C before exiting for me. Windows Python 3.9 – dbreaux Jul 05 '22 at 14:51
14

Not sure what the sense of this code is - but if necessary use a shorter sleep() interval and put a for loop around it:

for i in range(60):
   sleep(1)

Catching the KeyboardInterrupt exception using try..except is straight-forward

  • 2
    This can be extended for an even quicker reaction: for i in range(sec/0.1): sleep(0.1) – Ron Apr 16 '16 at 14:19
  • 2
    This might work for his naive example but there are many cases that one can't just simply poll faster. – Fardin Abdi Feb 28 '19 at 07:20
  • 5
    This is a hack. See the correct answer from @thom_nic. – Xvolks Dec 05 '19 at 16:46
  • 2
    This approach of trying something repeatedly is called "polling". Whenever possible it should be avoided, because it typically results in wasted CPU resources. Systems provide alternative mechanisms, such as those used by @thorn_nic. These are very efficient, because deep-down they utilize special hardware instructions and interrupts. – Diomidis Spinellis Jun 09 '20 at 13:05
  • And here's the solution: ` for i in range(360): try: sleep(1) except KeyboardInterrupt: sys.exit(0)` – Pithikos Nov 28 '21 at 16:22
7

The KeyboardInterrupt exception is raised when a user hits the interrupt key, Ctrl-C. In python this is translated from a SIGINT signal. That means, you can get handle it however you want using the signal module:

import signal

def handler(signum, frame):
    print("do whatever, like call thread.interrupt_main()")

signal.signal(signal.SIGINT, handler)
print("Waiting for SIGINT...")
signal.pause()

That way, you can do whatever you want at the receipt of a keyboard interrupt.

Erik Cederstrand
  • 9,643
  • 8
  • 39
  • 63
Pier1 Sys
  • 1,250
  • 2
  • 12
  • 21
  • 27
    interrupt is handled only after it wakes from sleep .. :( – Anuj Feb 25 '11 at 06:54
  • 1
    I think you might have something else going on in your code, as I can't reproduce it, or as you can see in the standard docs here: http://docs.python.org/library/time.html#time.sleep For example, is this happening on a different thread, perhaps something that is not a background thread? – Pier1 Sys Feb 26 '11 at 07:17
  • 2
    This only applies if you only have one primary threads. If you have other threads you start up, the signal handler won't deal with other threads that are sleeping. – djsumdog Sep 04 '15 at 20:11
  • 1
    Unfortunately this does not interrupt the `time.sleep()` call, even though your signal handler function will be executed. Only option would be to call `sys.exit()` from the signal handle but that's pretty brute-force. – thom_nic Sep 21 '17 at 14:07
  • 1
    `sleep()` in C can be interrupted by signal handler, but in Python, it won't. Quote from official Python 3.7 doc, _"Changed in version 3.5: The function now sleeps at least secs even if the sleep is interrupted by a signal, except if the signal handler raises an exception (see PEP 475 for the rationale)."_ – Bruce Aug 12 '19 at 20:15
  • Continue my comment. To make your answer work with the Python >= 3.5, add `raise` at the end of the definition of the handler function, and put `time.sleep()` in a try-except block. – Bruce Aug 12 '19 at 20:36
  • For me, on Linux w/Python 3.8, my signal handler didn't execute at all until after the sleep completed. – Ben Slade Mar 30 '21 at 19:24
4

The most elegant solution is certainly threading.Event, though if you only need a quick hack, this code works well :

import time

def main():
    print("It’s time !")

if __name__ == "__main__":
    print("press ctrl-c to stop")
    loop_forever = True
    while loop_forever:
        main()
        try:
            time.sleep(60)
        except KeyboardInterrupt:
            loop_forever = False
nico
  • 1,130
  • 2
  • 12
  • 26
  • In my test, on Linux with Python 3.8, when I hit control control-c during the sleep, the KeyboardInterrupt didn't get called until after the sleep was done. – Ben Slade Mar 30 '21 at 19:06
  • Strange that it didn’t work for you. I tried it on macOS, Raspberry Pi OS and Ubuntu and it works for me. – nico Dec 31 '21 at 18:31
  • I am using the exact same solution and it works on Debian. – Leonardo Hermoso Feb 25 '22 at 20:40
2

I tried your code with python versions 2.5, 2.6, 3 under Linux and all throw "KeyboardInterrupt" exception when hitting CTRL-C.

Maybe some exception handling catches the Interrupt or your problem is like this: Why is KeyboardInterrupt not working in python?

Community
  • 1
  • 1
johnbaum
  • 664
  • 4
  • 5
1

Based on @Andreas Jung answer

        for i in range(360):
            try:
                sleep(1)
            except KeyboardInterrupt:
                sys.exit(0)
Pithikos
  • 18,827
  • 15
  • 113
  • 136
0

Figured I'd throw this in.

import time


def sleep(seconds):
    for i in range(seconds):
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            print("Oh! You have sent a Keyboard Interrupt to me.\nBye, Bye")
            break


sleep(60)
xendi
  • 2,332
  • 5
  • 40
  • 64
  • I think you meant to use "break" not "continue" to stop sleeping. Using continue will cause it to just skip to the next iteration of the loop, which is essentially a noop. – Ben Slade Oct 27 '20 at 13:45