intervals with pythons threading
def setInterval(sec, func, *args, **kw): def inner(): func(*args, **kw) setInterval(sec, func, *args, **kw) # This is where it sends it again task = threading.Timer(sec, inner) task.daemon = True task.start() return task
As you can see, it works, however I have no way of canceling the thread because the original executes again and creates a new one before it can be canceled. How would I set this up so if the thread is canceled it won't create any copies of the same original thread? I've tried adding some keywords then sending the thread in it and have it cancel if it is a type of threading.Timer, but it doesn't seem to work since it already make a copy of the original before it could cancel, any ideas or suggestions? I'm just trying to think of a way so it knows that it's a copy of the original then not execute it but I'm not sure how I would actually do that. Is there anything I could do so it terminates/cancels the original thread and doesn't start a new copy of it before it has a chance to be canceled? Here is my attempt at doing it incase you want to tell me what I'm doing wrong.
def setInterval(sec = None, func = None, *args, **kw): start = True if type(func) != threading.Timer else func.cancel() # doesn't work anyways def inner(): func(*args, **kw) setInterval(sec, func, *args, **kw) if start == True: task = thrading.Timer(sec, inner) task.daemon = True task.start() return task def clearInterval(task): setInterval(func = task) myInterval = setInterval(10, print, "Hello, world!") clearInterval(myInterval) # cancels original, continues to make copies
Cancelling a Timer before it runs prevents it from calling its callback function. But if it's already woken up, it's too late to cancel it. And, since your code creates a new Timer inside the callback, that new Timer won't know it's supposed to be canceled.
So, you need to cooperate with the callback function, by giving it access to some flag that lets it know it's been canceled (e.g., with a mutable global or closure variable, function attribute, or default parameter value). And of course that variable needs to be synchronized, meaning you read it under a Lock, or you've got a race condition. (In CPython, thanks to the GIL, the worst-case scenario of that race should be occasionally running one extra time, but in a different implementation, it's possible that the timer thread (where the callback is running) could never see the updated value.)
However, this is going to get complicated. You're probably better off first extending the Timer class to a RepeatingTimer. Then, if you really want to you can wrap that in trivial setInterval/clearInterval functions.
There are plenty of recipes on ActiveState and packages on PyPI that add a repeat flag or equivalent, and also do other nice things like use a single thread instead of creating a new thread for every interval. But if you want to know how to do this yourself, it's pretty easy. In fact, the threading docs have a link to the threading.py source because it's meant to be useful as example code, and you can see how trivial Timer is, so you could even re-implement it yourself.
But let's do it with subclassing instead.
class RepeatableTimer(threading.Timer): def __init__(self, interval, function, args=None, kwargs=None, repeat=False): super(RepeatableTimer, self).__init__(interval, function, args, kwargs) self.repeat = repeat self.lock = threading.Lock() def cancel(self): with self.lock: self.repeat = False super(RepeatableTimer, self).cancel() def run(self): while True: self.finished.clear() super(RepeatableTimer, self).run() with self.lock: if not self.repeat: break
I think it would actually be simpler to implement it from scratch, because then you don't have to worry about resetting the Event, but anyway, this works.
Anyway, now you can wrap this in setInterval and clearInterval functions:
def setInterval(sec=None, func=None, *args, **kw): task = RepeatableTimer(sec, func, args, kw, repeat=True) task.daemon = True task.start() return task def clearInterval(task): task.cancel()
Although note that clearInterval = RepeatableTimer.cancel would work just as well. (In Python 2.x, this would be an unbound method vs. a function, but it would still work the same, other than giving different error messages if you called it with the wrong args. In 3.x, there is no difference at all.)
If you really want to do the whole mess with making clearInterval call setInterval, you can, but let's at least clean it up a bit—use isinstance instead of type, and don't try to set a flag that you use in a second if when you can just do it all in a single if:
def setInterval(sec=None, func=None, *args, **kw): if isinstance(func, RepeatableTimer): task.cancel() else: task = RepeatableTimer(sec, func, args, kw, repeat=True) task.daemon = True task.start() return task def clearInterval(task): setInterval(func=task)
But I don't see what you think that's buying you.
Anyway, here's a test to verify that it works:
myInterval = setInterval(1, print, "Hello, world!") time.sleep(3) clearInterval(myInterval) time.sleep(5)
This should usually print "Hello, world!" 2 or 3 times, occasionally 4, but never 7 or 8 like an uncancellable timer would.