Python - threading.Timer stays alive after calling cancel() method
Asked Answered
S

3

17

I noticed the following behavior in the following code (using threading.Timer class):

import threading

def ontimer():
    print threading.current_thread()

def main():
    timer = threading.Timer(2, ontimer)
    timer.start()
    print threading.current_thread()
    timer.cancel()
    if timer.isAlive():
        print "Timer is still alive"
    if timer.finished:
        print "Timer is finished"


 if __name__ == "__main__":
main()

The output of the code is:

<_MainThread(MainThread, started 5836)>
Timer is still alive
Timer is finished

As We notice from the output, that the timer object is still alive and finished in the same time.

In fact, I would like to call a similar function hundreds of times, and I wonder whether those "living" timers may affect the performance.

I would like to stop or cancel the timer object in a proper way. Am I doing it right?

Thank you

Schoolfellow answered 18/6, 2012 at 12:55 Comment(2)
Replace timer.finished with timer.finished.is_set(). The property timer.finished is not a boolean, it is an _Event object, so checking for if timer.finished is misleading (since that will always evaluate to True)Benitez
It just takes sometimes to considered "not alive", try to add sleep for 1 or 2 seconds after "timer.cancel()" and it will work properly .Manzo
D
13

A Timer is a subclass of a Thread and its implementation is really simple. It waits the provided time by subscribing to the event finished.

So when you set the event by Timer.cancel it is guaranteed that the function does not get called. But it is not guaranteed that the Timer thread will directly continue (and exit).

So the point is that the thread of the timer can still be alive after the execution of cancel, but the function will not get executed. So checking for finished is safe, while testing for Thread.is_alive (newer API, use this!) is a race condition in this case.

Hint: You can verify this by placing a time.sleep after calling cancel. Then it will just print:

<_MainThread(MainThread, started 10872)>
Timer is finished
Desirae answered 18/6, 2012 at 13:27 Comment(0)
M
14

You should use the thread.join() to wait until your timer's thread is really finished and cleaned.

import threading

def ontimer():
    print threading.current_thread()

def main():
    timer = threading.Timer(2, ontimer)
    timer.start()
    print threading.current_thread()
    timer.cancel()
    timer.join()         # here you block the main thread until the timer is completely stopped
    if timer.isAlive():
        print "Timer is still alive"
    else:
        print "Timer is no more alive"
    if timer.finished:
        print "Timer is finished"


 if __name__ == "__main__":
main()

This will display :

<_MainThread(MainThread, started 5836)>
Timer is no more alive
Timer is finished
Maurice answered 18/6, 2012 at 13:31 Comment(0)
D
13

A Timer is a subclass of a Thread and its implementation is really simple. It waits the provided time by subscribing to the event finished.

So when you set the event by Timer.cancel it is guaranteed that the function does not get called. But it is not guaranteed that the Timer thread will directly continue (and exit).

So the point is that the thread of the timer can still be alive after the execution of cancel, but the function will not get executed. So checking for finished is safe, while testing for Thread.is_alive (newer API, use this!) is a race condition in this case.

Hint: You can verify this by placing a time.sleep after calling cancel. Then it will just print:

<_MainThread(MainThread, started 10872)>
Timer is finished
Desirae answered 18/6, 2012 at 13:27 Comment(0)
U
0

None of the answers in this thread helped me to overcome the issue: the timer stays alive after a call to cancel() and still calls the timer callback function.

In my case this behavior made a pytest test suite hang and not finish until a timer in one of the tests expired.

I tried waiting on timer.is_alive() and calling timer.join() but the timer was still called.

What helped was to declare the timer with the daemon option set to True as follows:

timer = threading.Timer(timeout, expired_callback)
timer.daemon = True
...
...
timer.cancel()  # actually works.
Urata answered 12/8, 2024 at 13:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.