Skip to content

bpo-29569: threading.Timer class: Continue periodical execution till action return True #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions Doc/library/threading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
17 changes: 15 additions & 2 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -1081,13 +1082,25 @@ 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.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):
self.callback_args.append((args[:], kwargs.copy()))
self.callback_event.set()

def _callback_cont(self):
self.callback_cnt -= 1
self.callback_event.set()
return self.callback_cnt > 0

class LockTests(lock_tests.LockTests):
locktype = staticmethod(threading.Lock)

Expand Down
7 changes: 4 additions & 3 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to add test case when this is true. It might help you get 100% coverage

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. But: codecov/patch — 86.95% of diff hit (target 100%) - some red lines in Lib/test/test_threading.py.
I have no idea how to cover tests by tests =/

if not self.function(*self.args, **self.kwargs):
break
self.finished.set()

# Special thread class to represent the main thread
Expand Down