What is the best way to repeatedly execute a function every x seconds?

后端 未结 18 2680
不知归路
不知归路 2020-11-21 06:04

I want to repeatedly execute a function in Python every 60 seconds forever (just like an NSTimer in Objective C). This code will run as a daemon and is effectively like call

相关标签:
18条回答
  • 2020-11-21 06:10

    e.g., Display current local time

    import datetime
    import glib
    import logger
    
    def get_local_time():
        current_time = datetime.datetime.now().strftime("%H:%M")
        logger.info("get_local_time(): %s",current_time)
        return str(current_time)
    
    def display_local_time():
        logger.info("Current time is: %s", get_local_time())
        return True
    
    # call every minute
    glib.timeout_add(60*1000, display_local_time)
    
    0 讨论(0)
  • 2020-11-21 06:13

    I faced a similar problem some time back. May be http://cronus.readthedocs.org might help?

    For v0.2, the following snippet works

    import cronus.beat as beat
    
    beat.set_rate(2) # 2 Hz
    while beat.true():
        # do some time consuming work here
        beat.sleep() # total loop duration would be 0.5 sec
    
    0 讨论(0)
  • 2020-11-21 06:16

    Here's an adapted version to the code from MestreLion. In addition to the original function, this code:

    1) add first_interval used to fire the timer at a specific time(caller need to calculate the first_interval and pass in)

    2) solve a race-condition in original code. In the original code, if control thread failed to cancel the running timer("Stop the timer, and cancel the execution of the timer’s action. This will only work if the timer is still in its waiting stage." quoted from https://docs.python.org/2/library/threading.html), the timer will run endlessly.

    class RepeatedTimer(object):
    def __init__(self, first_interval, interval, func, *args, **kwargs):
        self.timer      = None
        self.first_interval = first_interval
        self.interval   = interval
        self.func   = func
        self.args       = args
        self.kwargs     = kwargs
        self.running = False
        self.is_started = False
    
    def first_start(self):
        try:
            # no race-condition here because only control thread will call this method
            # if already started will not start again
            if not self.is_started:
                self.is_started = True
                self.timer = Timer(self.first_interval, self.run)
                self.running = True
                self.timer.start()
        except Exception as e:
            log_print(syslog.LOG_ERR, "timer first_start failed %s %s"%(e.message, traceback.format_exc()))
            raise
    
    def run(self):
        # if not stopped start again
        if self.running:
            self.timer = Timer(self.interval, self.run)
            self.timer.start()
        self.func(*self.args, **self.kwargs)
    
    def stop(self):
        # cancel current timer in case failed it's still OK
        # if already stopped doesn't matter to stop again
        if self.timer:
            self.timer.cancel()
        self.running = False
    
    0 讨论(0)
  • 2020-11-21 06:16

    Here is another solution without using any extra libaries.

    def delay_until(condition_fn, interval_in_sec, timeout_in_sec):
        """Delay using a boolean callable function.
    
        `condition_fn` is invoked every `interval_in_sec` until `timeout_in_sec`.
        It can break early if condition is met.
    
        Args:
            condition_fn     - a callable boolean function
            interval_in_sec  - wait time between calling `condition_fn`
            timeout_in_sec   - maximum time to run
    
        Returns: None
        """
        start = last_call = time.time()
        while time.time() - start < timeout_in_sec:
            if (time.time() - last_call) > interval_in_sec:
                if condition_fn() is True:
                    break
                last_call = time.time()
    
    0 讨论(0)
  • 2020-11-21 06:17

    I use this to cause 60 events per hour with most events occurring at the same number of seconds after the whole minute:

    import math
    import time
    import random
    
    TICK = 60 # one minute tick size
    TICK_TIMING = 59 # execute on 59th second of the tick
    TICK_MINIMUM = 30 # minimum catch up tick size when lagging
    
    def set_timing():
    
        now = time.time()
        elapsed = now - info['begin']
        minutes = math.floor(elapsed/TICK)
        tick_elapsed = now - info['completion_time']
        if (info['tick']+1) > minutes:
            wait = max(0,(TICK_TIMING-(time.time() % TICK)))
            print ('standard wait: %.2f' % wait)
            time.sleep(wait)
        elif tick_elapsed < TICK_MINIMUM:
            wait = TICK_MINIMUM-tick_elapsed
            print ('minimum wait: %.2f' % wait)
            time.sleep(wait)
        else:
            print ('skip set_timing(); no wait')
        drift = ((time.time() - info['begin']) - info['tick']*TICK -
            TICK_TIMING + info['begin']%TICK)
        print ('drift: %.6f' % drift)
    
    info['tick'] = 0
    info['begin'] = time.time()
    info['completion_time'] = info['begin'] - TICK
    
    while 1:
    
        set_timing()
    
        print('hello world')
    
        #random real world event
        time.sleep(random.random()*TICK_MINIMUM)
    
        info['tick'] += 1
        info['completion_time'] = time.time()
    

    Depending upon actual conditions you might get ticks of length:

    60,60,62,58,60,60,120,30,30,60,60,60,60,60...etc.
    

    but at the end of 60 minutes you'll have 60 ticks; and most of them will occur at the correct offset to the minute you prefer.

    On my system I get typical drift of < 1/20th of a second until need for correction arises.

    The advantage of this method is resolution of clock drift; which can cause issues if you're doing things like appending one item per tick and you expect 60 items appended per hour. Failure to account for drift can cause secondary indications like moving averages to consider data too deep into the past resulting in faulty output.

    0 讨论(0)
  • 2020-11-21 06:18

    Lock your time loop to the system clock like this:

    import time
    starttime = time.time()
    while True:
        print "tick"
        time.sleep(60.0 - ((time.time() - starttime) % 60.0))
    
    0 讨论(0)
提交回复
热议问题