Index: Lib/threading.py =================================================================== --- Lib/threading.py (revision 54211) +++ Lib/threading.py (arbetskopia) @@ -625,6 +625,64 @@ self.function(*self.args, **self.kwargs) self.finished.set() +# Periodic timer class +class PeriodicTimer(Thread): + """ + Periodically call a function. + + PeriodicTimers are, like threads, started by calling their start() + method. An attempt to ending them can be made by calling their + end() method. + + PeriodicTimers use fixed-delay execution which means that the + delay between subsequent invokations of the function is + fixed. Because the execution time of the function is not accounted + for, the periods therefore become more and more distorted in + relation to real time. That makes PeriodicTimers unsuitable for + long running threads in which it is critical that each function + call time is as accurate as possible. + + For example: + + def hello(): + print "Hi there!" + + t = PeriodicTimer(5, hello) + t.start() # "Hi there!" will be printed every five seconds. + + """ + def __init__(self, interval, function, *args, **kwargs): + """ + Create a PeriodicTimer that will repeatedly call function with + arguments args and keyword arguments kwargs, after interval + seconds have passed. + """ + Thread.__init__(self) + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = Event() + + def end(self): + """ + Make a best effort attempt to stop the PeriodicTimer. + + If the thread is currently executing the function, this effort + may not immidiately succeed. The PeriodicTimer is then stopped + when execution returns from the function. If the function + never returns, the thread will not be stopped. + """ + self.finished.set() + + def run(self): + while True: + self.finished.wait(self.interval) + if self.finished.isSet(): + break + self.function(*self.args, **self.kwargs) + + # Special thread class to represent the main thread # This is garbage collected through an exit handler Index: Lib/test/test_threading.py =================================================================== --- Lib/test/test_threading.py (revision 54211) +++ Lib/test/test_threading.py (arbetskopia) @@ -3,6 +3,7 @@ import test.test_support from test.test_support import verbose import random +import sys import threading import thread import time @@ -201,8 +202,54 @@ t.join() # else the thread is still running, and we have no way to kill it +class PeriodicTimerTests(unittest.TestCase): + def append_item(self, list = None): + list.append('moo') + + # Ensure that negative periods behaves like if the period was 0. + def test_negative_period(self): + for period in (-1, -0.2, -33, -sys.maxint): + items = [] + pt = threading.PeriodicTimer(-1, self.append_item, items) + pt.start() + time.sleep(0.1) + pt.end() + self.assert_(len(items) > 0) + + def test_passing_kwargs(self): + items = [] + pt = threading.PeriodicTimer(0.025, self.append_item, list = items) + pt.start() + time.sleep(0.5) + pt.end() + self.assert_(len(items) > 0) + + # PeriodicTimer is alive until we call end(). After that, it is + # dead. + def test_life_then_death(self): + items = [] + pt = threading.PeriodicTimer(0, self.append_item, items) + pt.start() + for n in range(3): + self.assert_(pt.isAlive()) + time.sleep(0.1) + pt.end() + time.sleep(0.05) + self.assert_(not pt.isAlive()) + + # A PeriodicTimer ended before it is started will not run at all. + def test_ending_before_start(self): + items = [] + pt = threading.PeriodicTimer(0, self.append_item, items) + pt.end() + pt.start() + time.sleep(0.2) + pt.end() + self.assertEqual(items, []) + def test_main(): - test.test_support.run_unittest(ThreadTests) + test_classes = (ThreadTests, PeriodicTimerTests) + test.test_support.run_unittest(*test_classes) if __name__ == "__main__": test_main() Index: Doc/lib/libthreading.tex =================================================================== --- Doc/lib/libthreading.tex (revision 54211) +++ Doc/lib/libthreading.tex (arbetskopia) @@ -109,6 +109,10 @@ A thread that executes a function after a specified interval has passed. \end{classdesc*} +\begin{classdesc*}{PeriodicTimer}{} +A thread that periodically calls a function. +\end{classdesc*} + \begin{funcdesc}{settrace}{func} Set a trace function\index{trace function} for all threads started from the \module{threading} module. The \var{func} will be passed to @@ -696,6 +700,47 @@ will only work if the timer is still in its waiting stage. \end{methoddesc} + +\subsection{PeriodicTimer Objects \label{periodictimer-objects}} + +A subclass of \class{Thread} that periodically calls a function. + +PeriodicTimers are, like threads, started by calling their +\method{start()} method. An attempt to ending them can be made by +calling their \method{end()} method. + +PeriodicTimers use fixed-delay execution which means that the delay +between subsequent invokations of the function is fixed. Because the +execution time of the function is not accounted for, the periods +therefore become more and more distorted in relation to real +time. That makes PeriodicTimers unsuitable for long running threads in +which it is critical that each function call time is as accurate as +possible. + +For example: +\begin{verbatim} +def hello(name): + print 'Hi %s!' % name +t = PeriodicTimer(5, hello, 'Olle') +# 'Hi Olle!' will be printed every five seconds. +\end{verbatim} + +\begin{classdesc}{PeriodicTimer}{interval, function, args=(), kwargs=\{\}} +Create a PeriodicTimer that will repeatedly call \var{function} with +arguments \var{args} and keyword arguments \var{kwargs}, after +\var{interval} seconds has passed. +\end{classdesc} + +\begin{methoddesc}{end}{} +Make a best effort attempt to stop the PeriodicTimer. + +If the thread is currently executing the function, this effort may not +immidiately succeed. The PeriodicTimer is then stopped when execution +returns from the function. If the function never returns, the thread +will not be stopped. +\end{methoddesc} + + \subsection{Using locks, conditions, and semaphores in the \keyword{with} statement \label{with-locks}}