g-Off.net | Geoffrey Foster

18Mar/0915

A Python repeatable threading.Timer class

I had a function that I wanted to be called repeatedly every x seconds but couldn't find a nice way of doing this. The python threading module has a Timer class which will wait for the given number of seconds and then execute the passed in callable object. Unfortunately, once it has executed that code it is no longer useable. So based upon that class I whipped up the following RepeatableTimer. It's not "hard real-time" or anything since it doesn't account for the time taken to execute the given function, but it's good enough for what I needed. Feel free to use it however you see fit as I'm distributing it under the MIT license.

Usage:

Create a new RepeatTimer object passing it required arguments and any optional ones.

Required Arguments:

  • interval -- floating point number specifying the number of seconds to wait before executing function
  • function -- the function (or callable object) to be executed

Optional Arguments:

  • iterations -- integer specifying the number of iterations to perform
  • args -- list of positional arguments passed to function
  • kwargs -- dictionary of keyword arguments passed to function

Examples:

Execute the hello function every 5 seconds:

def hello():
	print "Hello World!"
 
r = RepeatTimer(5.0, hello)
r.start()

Execute the hello function every 5 seconds but terminate when it has executed 10 times:

def hello():
	print "Hello World!"
 
r = RepeatTimer(5.0, hello, 10)
r.start()
# Copyright (c) 2009 Geoffrey Foster
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
 
from threading import Event, Thread
 
class RepeatTimer(Thread):
    def __init__(self, interval, function, iterations=0, args=[], kwargs={}):
        Thread.__init__(self)
        self.interval = interval
        self.function = function
        self.iterations = iterations
        self.args = args
        self.kwargs = kwargs
        self.finished = Event()
 
    def run(self):
        count = 0
        while not self.finished.is_set() and (self.iterations <= 0 or count < self.iterations):
            self.finished.wait(self.interval)
            if not self.finished.is_set():
                self.function(*self.args, **self.kwargs)
                count += 1
 
    def cancel(self):
        self.finished.set()

Updated Oct 29, 2009: Removed extra unneeded Event object.

Share and Enjoy:
  • Digg
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Reddit
  • Twitter
Comments (15) Trackbacks (0)
  1. Code doesnt work for me unless I change .is_set for isSet

    def run(self):
    count = 0
    while not self.finished.isSet() and (self.iterations <= 0 or count < self.iterations):
    self.event.wait(self.interval)
    if not self.finished.isSet() and not self.event.isSet():
    self.function(*self.args, **self.kwargs)
    count += 1

  2. What version of python are you running? The code was written with 2.6 in mind which “provides PEP 8 compliant aliases and properties to replace the camelCase names that were inspired by Java’s threading API”. Previous versions will not have the is_set method (although isSet will do the exact same thing).

  3. Hi. Maybe this is a newbe question but I’m trying to use this timer within a class.
    I have a function which accepts no parameters and the timer I initialized within the body of the class. A second function (a button press event) successfully starts the timer, but when it comes time to run the function, I get an error. Here’s the code:

    def Log_In(self):
    print “Loggin in!”

    myTimer = repeattimer.RepeatTimer(5, Log_In)

    here’s the error:
    Exception in thread Thread-1:
    Traceback (most recent call last):
    File “C:\Python26\lib\threading.py”, line 525, in __bootstrap_inner
    self.run()
    File “C:\Python26\myPY\JCT Dorms\repeattimer.py”, line 43, in run
    self.function(*self.args, **self.kwargs)
    TypeError: Log_In() takes exactly 1 argument (0 given)

    running windows vista, Python 2.6.2

    Thank you!

  4. Your function Log_In is taking 1 argument (self), which is unnecessary because it’s a function on its own and not a method (where a method would be a function that is called on an object which is an instance of a class).

    Try:
    def Log_In():
      print "Loggin in!"

    for your function

  5. Hi, thanks for putting this up. I’m wondering why you have used two Event objects, self.event and self.finished. It seems to me that only one should be needed, and I was able to have it work with just one.

    But I’m fairly new to Python, and if there is a reason why you used two separate Event objects, could you mention why?

    Thanks!

  6. I can’t actually remember why I had the two Event objects but it would seem that you’re right at the extra one is unneeded. I’ve updated the code to reflect this. Thanks!

  7. When I’m running this code on MacOSX10.6 with Python 2.6.1 I can’t break script execution by pressing ctrl+c in terminal.

    Is there any way to make this work?

  8. You can break from a script using Ctrl+C (sends and EOF). Ctrl+C is for sending a KeyboardInterrupt.

    You can also cancel the execution of the timer by entering the command (for the above example script) r.cancel() and then exit normally.

    The interaction of KeyboardInterrupt and multi-threaded python is mentioned in the python docs, particularly:

    Threads interact strangely with interrupts: the KeyboardInterrupt exception will be received by an arbitrary thread. (When the signal module is available, interrupts always go to the main thread.)

    As mentioned in http://docs.python.org/library/thread.html

  9. I don’t understand why I can’t run your timer… I use it just to display a message every minute. The first time it run fine… but after that, an error occur and I don’t know why.

    File “/usr/lib/python2.6/threading.py”, line 525, in __bootstrap_inner
    self.run()
    File “./radio.py”, line 308, in run
    self.function(*self.args, **self.kwargs)
    TypeError: ‘NoneType’ object is not callable

    That’s the method :

    def summaryRefresh (self, message, startIt) :
    self.waiting_seconds = self.waiting_seconds – 60
    endIt = self.textBuffer.get_end_iter()
    self.textBuffer.delete(startIt, endIt)
    if (self.waiting_seconds < 60) :
    self.textBuffer.insert(startIt, "* Starting in a few moments…\n")
    else :
    message_displayed = message % (int(self.waiting_seconds / 60))
    self.textBuffer.insert(startIt, message_displayed)

    Could you help me please?

  10. This is exactly what I need for my wallpaper script, except I’d like to be able to start and stop it. I saw in your last post you you can use r.cancel() to stop it, but you can’t just restart with r.start() since it gives the error “RuntimeError: thread already started”.

    Is there a simple way to restart the timer after it’s been stopped?

  11. I keep getting this error:

    Traceback (most recent call last):
    File “MentionPoll.py”, line 51, in
    initTwitterPolling()
    File “MentionPoll.py”, line 47, in initTwitterPolling
    r = RepeatTimer(10.0, sinceIDReply, args=since_id)
    TypeError: ‘module’ object is not callable

    Any ideas what it could be?

  12. No, like all python threads once it has been stopped it is no longer valid and can’t be restarted. You could of course add something into the run method that would check a “paused” boolean value, which, if true, could then cause the thread to sleep for a certain period of time

  13. Based upon the error and the snippet of code (without seeing more of it) I’d have to guess that sinceIDReply is a module and not a function or object (the object must implement the __call__ method for it to be callable)

  14. Hi, I have used the code with python 2.5 and it works ok (with the change of IsSet) but when I generate an executable with pyexe, and I execute the application I get an error like this:
    Unhandled exception in thread started by <bound method RepeatTimer.__bootstrap of >
    Traceback (most recent call last):
    File “threading.pyc”, line 462, in __bootstrap
    File “threading.pyc”, line 527, in __bootstrap_inner
    AttributeError: ‘NoneType’ object has no attribute ‘acquire’

    Can anybody help me?

  15. Wow, thank you so much. This is exactly what I needed for my gps/nmea logger. I’m not a programmer and it took me all day just to get those 15 lines working. I thought it would take me another two to get threading sorted.

    Thanks again


Leave a comment


No trackbacks yet.