From 50862668b1893346410a2d920cdf63bd776a51a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 25 Mar 2015 17:24:12 +0100 Subject: [PATCH] RepeatedTimer now allows adaptive interval and dynamic loop condition --- src/octoprint/util/__init__.py | 62 +++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 6aa8d0e8..c16dbeef 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -536,7 +536,7 @@ def address_for_client(host, port): class RepeatedTimer(threading.Thread): """ - This class represents an action that should be run repeatedly in a predefined interval. It is similar to python's + This class represents an action that should be run repeatedly in an interval. It is similar to python's own :class:`threading.Timer` class, but instead of only running once the ``function`` will be run again and again, sleeping the stated ``interval`` in between. @@ -554,48 +554,92 @@ class RepeatedTimer(threading.Thread): t = RepeatedTimer(1.0, hello) t.start() # prints "Hello World!" every second + Another example with dynamic interval and loop condition: + + .. code-block:: python + + count = 0 + maximum = 5 + factor = 1 + + def interval(): + global count + global factor + return count * factor + + def condition(): + global count + global maximum + return count <= maximum + + def hello(): + print("Hello World!") + + global count + count += 1 + + t = RepeatedTimer(interval, hello, run_first=True, condition=condition) + t.start() # prints "Hello World!" 5 times, printing the first one + # directly, then waiting 1, 2, 3, 4s in between (adaptive interval) + Arguments: - interval (float): The interval between each ``function`` call, in seconds. + interval (float or callable): The interval between each ``function`` call, in seconds. Can also be a callable + returning the interval to use, in case the interval is not static. function (callable): The function to call. - args (list or tuple): The arguments for the ``function`` call. - kwargs (dict): The keyword arguments for the ``function`` call. + args (list or tuple): The arguments for the ``function`` call. Defaults to an empty list. + kwargs (dict): The keyword arguments for the ``function`` call. Defaults to an empty dict. run_first (boolean): If set to True, the function will be run for the first time *before* the first wait period. If set to False (the default), the function will be run for the first time *after* the first wait period. + condition (callable): Condition that needs to be True for loop to continue. Defaults to ``lambda: True``. """ - def __init__(self, interval, function, args=None, kwargs=None, run_first=False): + def __init__(self, interval, function, args=None, kwargs=None, run_first=False, condition=None): threading.Thread.__init__(self) if args is None: args = [] if kwargs is None: kwargs = dict() + if condition is None: + condition = lambda: True + + if not callable(interval): + self.interval = lambda: interval + else: + self.interval = interval - self.interval = interval self.function = function self.finished = threading.Event() self.args = args self.kwargs = kwargs self.run_first = run_first + self.condition = condition def cancel(self): self.finished.set() def run(self): - while True: + while self.condition(): if self.run_first: # if we are to run the function BEFORE waiting for the first time self.function(*self.args, **self.kwargs) + # make sure our condition is still met before running into the downtime + if not self.condition(): + break + # wait, but break if we are cancelled - self.finished.wait(self.interval) + self.finished.wait(self.interval()) if self.finished.is_set(): - return + break if not self.run_first: # if we are to run the function AFTER waiting for the first time self.function(*self.args, **self.kwargs) + # make sure we set our finished event so we can detect that the loop was finished + self.finished.set() + class CountedEvent(object):