From 7895315920754af6f8742e6bb276acd74a6e00f9 Mon Sep 17 00:00:00 2001 From: Sly_tom_cat Date: Wed, 15 Feb 2017 17:05:34 +0300 Subject: [PATCH 1/5] Change in threading.Timer class: Continue periodical execution until action returns True. --- Doc/library/threading.rst | 32 ++++++++++++++++++++++---------- Lib/test/test_threading.py | 12 ++++++++++-- Lib/threading.py | 7 ++++--- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 2792dfdce04c6c..d3678a97b23cfd 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -831,29 +831,41 @@ method. The :meth:`~Event.wait` method blocks until the flag is true. Timer Objects ------------- -This class represents an action that should be run only after a certain amount -of time has passed --- a timer. :class:`Timer` is a subclass of :class:`Thread` +This class represents an action that should be run after a certain amount +of time has passed --- a timer. It also can run periodically: each run happens +after specified time that passed since previous run until the action returns True. +:class:`Timer` is a subclass of :class:`Thread` and as such also functions as an example of creating custom threads. Timers are started, as with threads, by calling their :meth:`~Timer.start` method. The timer can be stopped (before its action has begun) by calling the -:meth:`~Timer.cancel` method. The interval the timer will wait before -executing its action may not be exactly the same as the interval specified by -the user. +:meth:`~Timer.cancel` method. If action has returned True then next run is +scheduled after the timer interval. The interval the timer will wait before +executing its action may not be exactly the same as the interval specified +by the user. + For example:: - def hello(): - print("hello, world") + def star(): + global cnt + print("*") + cnt -= 1 + if cnt > 0: + return True + + cnt = 5 + t = Timer(1.0, star) + t.start() # it prints five "*" with 1 sec waiting between prints - t = Timer(30.0, hello) - t.start() # after 30 seconds, "hello, world" will be printed + .. class:: Timer(interval, function, args=None, kwargs=None) Create a timer that will run *function* with arguments *args* and keyword - arguments *kwargs*, after *interval* seconds have passed. + arguments *kwargs*, after *interval* seconds have passed and continues + periodically run *function* until *function* returns True. If *args* is ``None`` (the default) then an empty list will be used. If *kwargs* is ``None`` (the default) then an empty dict will be used. diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 2c2914fd6d8969..fa613e37c7b8e3 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1067,6 +1067,7 @@ class TimerTests(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) self.callback_args = [] + self.callback_cnt = 3 self.callback_event = threading.Event() def test_init_immutable_default_args(self): @@ -1081,13 +1082,20 @@ def test_init_immutable_default_args(self): timer2 = threading.Timer(0.01, self._callback_spy) timer2.start() self.callback_event.wait() - self.assertEqual(len(self.callback_args), 2) - self.assertEqual(self.callback_args, [((), {}), ((), {})]) + + def test_continuous_execution(self): + timer = threading.Timer(0.01, self._callback_cont) + self.callback_event.wait(0.1) + self.assertEqual(self.callback_cnt, 0) def _callback_spy(self, *args, **kwargs): self.callback_args.append((args[:], kwargs.copy())) self.callback_event.set() + def _callback_cont(self): + self.callback_cnt -= 1 + return self.callback_cnt > 0 + class LockTests(lock_tests.LockTests): locktype = staticmethod(threading.Lock) diff --git a/Lib/threading.py b/Lib/threading.py index 4829ff426e0bfd..9cf6cef6652218 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1177,9 +1177,10 @@ def cancel(self): self.finished.set() def run(self): - self.finished.wait(self.interval) - if not self.finished.is_set(): - self.function(*self.args, **self.kwargs) + """Continue execution after wait until function returns True""" + while(not self.finished.wait(self.interval)): + if not self.function(*self.args, **self.kwargs): + break self.finished.set() # Special thread class to represent the main thread From cfac9a122a840bac637fe28998d00c20058cd825 Mon Sep 17 00:00:00 2001 From: Sly_tom_cat Date: Wed, 15 Feb 2017 22:51:43 +0300 Subject: [PATCH 2/5] Fix for threading tests for Timer class --- Lib/test/test_threading.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index fa613e37c7b8e3..1a49e32bcd5b01 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1085,7 +1085,11 @@ def test_init_immutable_default_args(self): def test_continuous_execution(self): timer = threading.Timer(0.01, self._callback_cont) - self.callback_event.wait(0.1) + self.callback_event.clear() + timer.start() + for i in range(3): + self.callback_event.wait(1) + self.callback_event.clear() self.assertEqual(self.callback_cnt, 0) def _callback_spy(self, *args, **kwargs): @@ -1094,6 +1098,7 @@ def _callback_spy(self, *args, **kwargs): def _callback_cont(self): self.callback_cnt -= 1 + self.callback_event.set() return self.callback_cnt > 0 class LockTests(lock_tests.LockTests): From 178568b02a4f5800207ae3b9a92922a897f64760 Mon Sep 17 00:00:00 2001 From: Sly_tom_cat Date: Thu, 16 Feb 2017 10:27:05 +0300 Subject: [PATCH 3/5] Test coverage increased --- Lib/test/test_threading.py | 23 ++++++++++++++++------- Lib/threading.py | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 1a49e32bcd5b01..b8aba82d92e084 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1082,19 +1082,28 @@ def test_init_immutable_default_args(self): timer2 = threading.Timer(0.01, self._callback_spy) timer2.start() self.callback_event.wait() + self.assertEqual(len(self.callback_args), 2) + self.assertEqual(self.callback_args, [((), {}), ((), {})]) + + def _callback_spy(self, *args, **kwargs): + self.callback_args.append((args[:], kwargs.copy())) + self.callback_event.set() def test_continuous_execution(self): - timer = threading.Timer(0.01, self._callback_cont) + timer1 = threading.Timer(0.01, self._callback_cont) self.callback_event.clear() - timer.start() + timer1.start() for i in range(3): - self.callback_event.wait(1) + self.callback_event.wait(1.0) self.callback_event.clear() self.assertEqual(self.callback_cnt, 0) - - def _callback_spy(self, *args, **kwargs): - self.callback_args.append((args[:], kwargs.copy())) - self.callback_event.set() + self.callback_cnt = 3 + timer2 = threading.Timer(0.5, self._callback_cont) + self.callback_event.clear() + timer2.start() + timer2.cancel() + timer2.join(2.0) + self.assertEqual(self.callback_cnt, 3) def _callback_cont(self): self.callback_cnt -= 1 diff --git a/Lib/threading.py b/Lib/threading.py index 9cf6cef6652218..1b639f6862941d 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1177,7 +1177,7 @@ def cancel(self): self.finished.set() def run(self): - """Continue execution after wait until function returns True""" + """Continue execution after wait till function returns True""" while(not self.finished.wait(self.interval)): if not self.function(*self.args, **self.kwargs): break From e25ab6af0e2aa56cb189ebb19e9c7199f8a96559 Mon Sep 17 00:00:00 2001 From: Sly_tom_cat Date: Thu, 16 Feb 2017 10:45:09 +0300 Subject: [PATCH 4/5] fix docs --- Doc/library/threading.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index d3678a97b23cfd..210a450462f371 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -832,9 +832,9 @@ Timer Objects ------------- This class represents an action that should be run after a certain amount -of time has passed --- a timer. It also can run periodically: each run happens -after specified time that passed since previous run until the action returns True. -:class:`Timer` is a subclass of :class:`Thread` +of time has passed --- a timer. It also can run periodically. Each run takes +place after a specified time after the previous run. This continues until +the action returns TRUE. :class:`Timer` is a subclass of :class:`Thread` and as such also functions as an example of creating custom threads. Timers are started, as with threads, by calling their :meth:`~Timer.start` @@ -842,7 +842,7 @@ method. The timer can be stopped (before its action has begun) by calling the :meth:`~Timer.cancel` method. If action has returned True then next run is scheduled after the timer interval. The interval the timer will wait before executing its action may not be exactly the same as the interval specified -by the user. +by the user. For example:: @@ -858,14 +858,12 @@ For example:: t = Timer(1.0, star) t.start() # it prints five "*" with 1 sec waiting between prints - - .. class:: Timer(interval, function, args=None, kwargs=None) Create a timer that will run *function* with arguments *args* and keyword arguments *kwargs*, after *interval* seconds have passed and continues - periodically run *function* until *function* returns True. + periodically run *function* till *function* returns True. If *args* is ``None`` (the default) then an empty list will be used. If *kwargs* is ``None`` (the default) then an empty dict will be used. From ab382d0b1385ce5bded2a9102083f306c51eb14a Mon Sep 17 00:00:00 2001 From: Sly_tom_cat Date: Fri, 17 Feb 2017 10:25:31 +0300 Subject: [PATCH 5/5] increased test coverage --- Lib/test/test_threading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index b8aba82d92e084..e95f64df772492 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1088,6 +1088,7 @@ def test_init_immutable_default_args(self): def _callback_spy(self, *args, **kwargs): self.callback_args.append((args[:], kwargs.copy())) self.callback_event.set() + self.assertEqual(self.callback_event.is_set(), True) def test_continuous_execution(self): timer1 = threading.Timer(0.01, self._callback_cont) @@ -1108,6 +1109,7 @@ def test_continuous_execution(self): def _callback_cont(self): self.callback_cnt -= 1 self.callback_event.set() + self.assertEqual(self.callback_event.is_set(), True) return self.callback_cnt > 0 class LockTests(lock_tests.LockTests):