Waht do you think about my repeated_timer class

Martin Di Paola martinp.dipaola at gmail.com
Fri Feb 4 18:07:54 EST 2022


>In _run I first set the new timer and then I execute the function. So
>that will go mostly OK.

Yes, that's correct however you are not taking into consideration the 
imprecision of the timers.

Timer will call the next _run() after self._interval *plus* some unknown 
arbitrary time (and extra delay).

Let's assume that when you setup an 1 sec Timer but the Timer calls 
_run() after 1.01 secs due this unknown extra delay.

The first time fn() should be called after 1 sec since the begin but it 
is called after 1.01 secs so the extra delay was of 0.01 sec.

The second time fn() should be called after 2 secs since the begin but 
it is called after 2.02 secs. The second fn() was delayed not by 0.01 
but by 0.02 secs.

The third fn() will be delayed by 0.03 secs and so on.

This arbitrary delay is very small however it will sum up on each 
iteration and depending of your application can be a serious problem.

I wrote a post about this and how to create "constant rate loops" which 
fixes this problem:
https://book-of-gehn.github.io/articles/2019/10/23/Constant-Rate-Loop.html

In the post I also describe two solutions (with their trade-offs) for 
when the target function fn() takes longer than the self._interval time.

See if it helps.

Thanks,
Martin.


On Thu, Feb 03, 2022 at 11:41:42PM +0100, Cecil Westerhof via 
Python-list wrote:
>Barry <barry at barrys-emacs.org> writes:
>
>>> On 3 Feb 2022, at 04:45, Cecil Westerhof via Python-list <python-list at python.org> wrote:
>>>
>>> Have to be careful that timing keeps correct when target takes a 'lot'
>>> of time.
>>> Something to ponder about, but can wait.
>>
>> You have noticed that your class does call the function at the repeat interval but
>> rather at the repeat interval plus processing time.
>
>Nope:
>        def _next(self):
>            self._timer = Timer(self._interval, self._run)
>            self._timer.start()
>
>        def _run(self):
>            self._next()
>            self._fn()
>
>In _run I first set the new timer and then I execute the function. So
>that will go mostly OK.
>
>
>> The way to fix this is to subtract the last processing elapsed time for the next interval.
>> Sort of a software phase locked loop.
>>
>> Just before you call the run function record the time.time() as start_time.
>> Then you can calculate next_interval = max( .001, interval - time.time() - start_time)
>> I use 1ms as the min interval.
>
>But I am working on a complete rewrite to create a more efficient
>class. (This means I have to change also the code that uses it.) There
>I have to do something like you suggest. (I am already working on it.)
>
>
>Personally I am also of the opinion that the function should finish in
>less as 10% from the interval. (That was one of my rewrites.)
>
>-- 
>Cecil Westerhof
>Senior Software Engineer
>LinkedIn: http://www.linkedin.com/in/cecilwesterhof
>-- 
>https://mail.python.org/mailman/listinfo/python-list


More information about the Python-list mailing list