5

What I want is to be able to run a function every second, irrelevant of how long the function takes (it should always be under a second). I've considered a number of options but not sure which is best.

If I just use the delay function it isn't going to take into account the time the function takes to run.

If I time the function and then subtract that from a second and make up the rest in the delay it's not going to take into account the time calculations.

I tried using threading.timer (I'm not sure about the ins and outs of how this works) but it did seem to be slower than the 1s.

Here's the code I tried for testing threading.timer:

def update(i):
    sys.stdout.write(str(i)+'\r')
    sys.stdout.flush()
    print i
    i += 1
    threading.Timer(1, update, [i]).start()

Is there a way to do this irrelevant of the length of the time the function takes?

DNN
  • 861
  • 2
  • 11
  • 17
  • Another solution of mine http://stackoverflow.com/a/10718961/709852 should work for this. Actually, the solution is better suited since the timing resolution seems to be lower and it will likely work cross platform. – Henry Gomersall May 30 '12 at 09:18
  • do you want it to loop in a background thread or in the main thread? – moooeeeep May 30 '12 at 09:36
  • related: [What is the best way to repeatedly execute a function every x seconds in Python?](http://stackoverflow.com/q/474528/4279) – jfs Feb 28 '16 at 19:21

6 Answers6

10

This will do it, and its accuracy won't drift with time.

import time

start_time = time.time()
interval = 1
for i in range(20):
    time.sleep(start_time + i*interval - time.time())
    f()
Rodrigo Queiro
  • 1,324
  • 8
  • 15
  • Busy wait loops are now generally looked down upon for the reason you state...even if they appear to work. – martineau May 30 '12 at 12:35
  • Just replaced it with a sleep to improve that. – Rodrigo Queiro May 30 '12 at 14:10
  • to avoid subtracting close floating point numbers, you could [use `time.sleep(1 - time.monotonic() % 1))` here for the same effect (to avoid drift)](http://stackoverflow.com/a/35687118/4279). – jfs Feb 28 '16 at 19:23
7

The approach using a threading.Timer (see code below) should in fact not be used, as a new thread is launched at every interval and this loop can never be stopped cleanly.

# as seen here: https://stackoverflow.com/a/3393759/1025391
def update(i):
  threading.Timer(1, update, [i+1]).start()
  # business logic here

If you want a background loop it is better to launch a new thread that runs a loop as described in the other answer. Which is able to receive a stop signal, s.t. you can join() the thread eventually.

This related answer seems to be a great starting point to implement this.

moooeeeep
  • 31,622
  • 22
  • 98
  • 187
  • Seems wrong [according to this comment](https://stackoverflow.com/questions/3393612/run-certain-code-every-n-seconds/3393759#comment92673741_3393759) – lucid_dreamer Oct 29 '18 at 09:28
  • @lucid_dreamer The reasoning about recursion depth is incorrect (new threads don't increase recursion depth). But I agree that this is not how one would implement this ideally. Reasons being launching new threads over and over again, and the inability to stop the loop eventually. – moooeeeep Oct 29 '18 at 11:14
  • I see. However still strange that he reported a quoted error "RuntimeError: maximum recursion depth exceeded". – lucid_dreamer Oct 30 '18 at 12:13
6

if f() always takes less than a second then to run it on a one second boundary (without a drift):

import time

while True:
    time.sleep(1 - time.monotonic() % 1)      
    f()

The idea is from @Dave Rove's answer to a similar question.

To understand how it works, consider an example:

  1. time.monotonic() returns 13.7 and time.sleep(0.3) is called
  2. f() is called around (±some error) 14 seconds (since time.monotonic() epoch)
  3. f() is run and it takes 0.1 (< 1) seconds
  4. time.monotonic() returns around 14.1 seconds and time.sleep(0.9) is called
  5. Step 2. is repeated around 15 seconds (since time.monotonic() epoch)
  6. f() is run and it takes 0.3 (< 1) seconds (note: the value is different from Step 2.)
  7. time.monotonic() returns around 15.3 and time.sleep(0.7) is called
  8. f() is called around 16 seconds and the loop is repeated.

At each step f() is called on a one second boundary (according to time.monotonic() timer). The errors do not accumulate. There is no drift.

See also: How to run a function periodically in python (using tkinter).

Jason Webster
  • 11
  • 1
  • 2
jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

How about this: After each run, sleep for (1.0 - launch interval) seconds. You can change the terminate condition by changing while True:. Although if the your function takes more than 1 second to run, this will go wrong.

from time import time, sleep

while True:
    startTime = time()
    yourFunction()
    endTime = time()-startTime
    sleep(1.0-endTime)
lllluuukke
  • 1,304
  • 2
  • 13
  • 17
1

Threading may be a good choice. The basic concept is as follows.

import threading

def looper():    
    # i as interval in seconds    
    threading.Timer(i, looper).start()    
    # put your action here
    foo()

#to start 
looper()
Znatz
  • 1,530
  • 2
  • 18
  • 31
-1

I would like to recommend the following code. You can replace the True with any condition if you want.

while True:
time.sleep(1) #sleep for 1 second
func()    #function you want to trigger

Tell me if it works.

Grasshopper
  • 363
  • 2
  • 14