105

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

3
  • 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. Commented Feb 25, 2011 at 6:41
  • 1
    In most systems, Ctrl-C would interrupt the sleep. It certainly does on Unix and on Mac. Which system are you on? Commented Feb 25, 2011 at 7:23
  • 1
    for i in range(60): sleep(1) its better i think Commented Mar 1, 2011 at 10:45

7 Answers 7

222

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
import signal

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__':
    signal.signal(signal.SIGTERM, quit)
    signal.signal(signal.SIGINT, quit)
    signal.signal(signal.SIGHUP, 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.

Sign up to request clarification or add additional context in comments.

9 Comments

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
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 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 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.
I wanted this to work, but it still waits 60s after ctrl-C before exiting for me. Windows Python 3.9
|
17

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

6 Comments

This can be extended for an even quicker reaction: for i in range(sec/0.1): sleep(0.1)
This might work for his naive example but there are many cases that one can't just simply poll faster.
This is a hack. See the correct answer from @thom_nic.
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.
And here's the solution: ` for i in range(360): try: sleep(1) except KeyboardInterrupt: sys.exit(0)`
|
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.

7 Comments

interrupt is handled only after it wakes from sleep .. :(
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: 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?
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.
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.
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)."
|
5

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

3 Comments

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.
Strange that it didn’t work for you. I tried it on macOS, Raspberry Pi OS and Ubuntu and it works for me.
I am using the exact same solution and it works on Debian.
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?

Comments

1

Based on @Andreas Jung answer

        for i in range(360):
            try:
                sleep(1)
            except KeyboardInterrupt:
                sys.exit(0)

Comments

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)

2 Comments

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.
All this does is prevent the failing case, eating the exception, if that's desired.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.