2

My goal is to execute a function on a periodic time interval but with a high period. The code linked below seemed very promising:
https://medium.com/greedygame-engineering/an-elegant-way-to-run-periodic-tasks-in-python-61b7c477b679. With some minor modification i ended up with this:

import threading
import time
from datetime import timedelta
from unittest.mock import Mock

WAIT_TIME_SECONDS = 0.1

class PeriodicTask(threading.Thread):
    """ Class for executing a periodic task specifying a time interval between invokations """
    def __init__(self, interval: timedelta, execute: Callable[..., None], *args, **kwargs):
        super().__init__()
        assert isinstance(interval, timedelta), "Must specifiy datetime time interval, here"
        assert not execute is None, "Must specify function which should be invoked regularly, here"

        self.daemon = False
        self.stopped = threading.Event()
        self.interval = interval
        self.execute = execute
        self.args = args
        self.kwargs = kwargs

    def stop(self):
        """ Stop periodic task """
        self.stopped.set()
        self.join()

    def run(self):
        """ Run task based on the specified interval """
        while not self.stopped.wait(self.interval.total_seconds()):
            self.execute(*self.args, **self.kwargs)

if __name__ == "__main__":
    foo = Mock()
    job = PeriodicTask(interval=timedelta(seconds=WAIT_TIME_SECONDS), execute=foo)
    job.start()

    time.sleep(1)
    job.stop()

It seems i can execute periodic tasks down to a period of approx 100ms (Intel Core i7-3770K CPU, 3.5 GHz, 16 GB RAM), before tasks hinder each other. Is there a way to optimize this code fragment so i can execute tasks periodically down to at least 10ms?

  • Have you tried parallelization? – Abhinav Mathur Oct 19 '20 at 11:53
  • I wanted to execute the specified function every time the given interval is elapsed. I did not want to execute it in parallel. The callback function has the role of an "observer" observing some state in the end. But this is only the minimal example. I should not have called it "foo". My fault. I should have called it indeed: "observer" – Wör Du Schnaffzig Oct 19 '20 at 11:59
  • Add the following line to the code: `print (f"Called function {foo.call_count} times.")` and reduce the `WAIT_TIME_SECONDS` until the `call_count` will not change it's value anymore. – Wör Du Schnaffzig Oct 19 '20 at 12:03
  • @pqans Every operating system has its limits when it comes to being real time. Add to that the overhead of what you expect to be doing and you'll quickly get some limits. With your code decreasing `WAIT_TIME_SECONDS` gives linear results until about 100ms, but it will still change even with a value of `0.00001`, even though it will not be `90000` but lower (I get about 25.000 for example). For me it stops changing at `0.0000001`, which is 1/10.000.000 of a second, but it "only" displays 100.000 runs (not ten millions). At some point any code you write will make it worse. – ChatterOne Oct 19 '20 at 12:12
  • Seems the only way is to fall back to oldschool polling using time.perf_counter. On the other hand i don't want to scatter perf_counters all over my code. – Wör Du Schnaffzig Oct 19 '20 at 13:00
  • @pqans Most likely perf_counter will also not do what you want. The accuracy is dependent on the OS. You might be able to squeeze a bit more out of it because you'll have less code, but at some point the same limits will start to apply. And BTW, this also include unpredictability in terms of how long some operation will take. – ChatterOne Oct 19 '20 at 14:11

0 Answers0