Run certain code every n seconds [duplicate]
Asked Answered
D

7

219

Is there a way to, for example, print Hello World! every n seconds? For example, the program would go through whatever code I had, then once it had been 5 seconds (with time.sleep()) it would execute that code. I would be using this to update a file though, not print Hello World.

For example:

startrepeat("print('Hello World')", .01) # Repeats print('Hello World') ever .01 seconds

for i in range(5):
    print(i)

>> Hello World!
>> 0
>> 1
>> 2
>> Hello World!
>> 3
>> Hello World!
>> 4
Dayan answered 3/8, 2010 at 4:40 Comment(3)
Related: What is the best way to repeatedly execute a function every x seconds in Python?Munafo
@Munafo this question is a little different to your link, as this question is wanting to run code asynchronously to the main code execution.Molloy
@101: I don't know whether you see it, but I haven't closed neither of the questions. btw, [RepeatedTimer](btw,https://mcmap.net/q/74048/-how-to-repeatedly-execute-a-function-every-x-seconds) seems answers the current question. Here's a variant that doesn't start a new thread on each iteration or asyncio-based if you need one.Munafo
W
440
import threading

def printit():
  threading.Timer(5.0, printit).start()
  print "Hello, World!"

printit()

# continue with the rest of your code

https://docs.python.org/3/library/threading.html#timer-objects

Wistrup answered 3/8, 2010 at 5:16 Comment(22)
This isn't working either - it will just print hello world then do whatever code I put after it, without repeating itself.Dayan
you can probably create a method SetAlarm which takes all require arguments to execute the function and this method will set threading.Timer and just return, I mean like this.......... timer = threading.Timer(wait, fn, args, kwargs) you can also use mx DateTime to calculate the time interval using DateTime.RelativeDateTime(days=interval), timer.start()Genevivegenevra
@Zonda, oops, forgot to start the thread -- editing now to fix, sorry.Wistrup
OK, fixed, note that you may also want to set the thread made by Timer as a daemon in case you want to interrupt the program cleanly by just finishing the main thread -- in that case you'd better set t = threading.Timer &c, then t.daemon = True, and only then t.start() right before the print "Hello, World!".Wistrup
@shah, yes, the idea can be wrapped in a more general function (not necessarily a method -- personally, if I needed that, I'd make it a decorator-style nested function), though the repeated calls to threading.Timer are a must, be they directly in the function that must repeat, or a smart general wrapper for it. mx.DateTime is pretty obsolete since Python's standard library gained module datetime (many years ago) and wouldn't help anyway for the "repeat every 5 seconds" spec the OP posed in their question.Wistrup
This seems not to resolve the question at all... it does not repeat every second.Accentual
@YanKingYin: try to run it. It does repeat. printit schedules itself.Munafo
It is important to understand that this timer fires only once (unlike other known timers that fire repeatedly). Therefore it has to restart itself every 5 seconds.Musicale
It is printing the thing twice . I think the function is being called twice.Ampulla
How would you prevent multiple instances of this starting? For example if you scheduled something to run every 5 seconds but it took 10 seconds to complete?Notarize
Finally figured out this unclear piece of code! The last line is not a method definition and it is not inside printit. It is a call to the printit method from somewhere else. And if you don't add t.daemon = True as suggested, then the IDE crashes.Marcenemarcescent
Very interesting. I'm accustomed to doing this in JavaScript which is quite different. Excellent answer (+1) ;)Canaletto
This is so helpful!Mm
wouldn't this eventually hit the recursive function limit?Pluckless
How do I stop it?Outofdate
This code it totally wrong, It not only cause "RuntimeError: maximum recursion depth exceeded" but also will not provide a way to stop it. As some answer bellow the way to go is using a schedulerCollettecolletti
@Collettecolletti There is no recursion. But if the runtime of the function exceeds the interval you may end up with something similar to a fork bomb.Africander
@Africander : You are calling threading.Timer(5.0, printit).start() print "Hello, World!" from within def prinit() function.. I don't know how you call a function that calls it self.. in my side of the world we call it recursive.Collettecolletti
@Collettecolletti Calling the function recursively is not the same as launching a new thread to run the function. The first adds to the recursion depth of the current thread and the other doesn't.Africander
@DakshShah: you don't, as start/stop control was not requested by the OP. For such control, see my answer below.Newmown
@AbelSurace: It has no recursion because it launches in another thread and printit() continues to the end. Your side of the world is a single-threaded one. Welcome to the multi-threading side :-)Newmown
Note that this code doesn't actually address the question: this runs a function, once. Where's the rest of the code that effects the interval call (without blocking the main thread, and without making new threads over and over?)Circumspect
N
158

My humble take on the subject, a generalization of Alex Martelli's answer, with start() and stop() control:

from threading import Timer

class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.interval   = interval
        self.function   = function
        self.args       = args
        self.kwargs     = kwargs
        self.is_running = False
        self.start()
    
    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)
    
    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True
    
    def stop(self):
        self._timer.cancel()
        self.is_running = False

Usage:

from time import sleep

def hello(name):
    print "Hello %s!" % name

print "starting..."
rt = RepeatedTimer(1, hello, "World") # it auto-starts, no need of rt.start()
try:
    sleep(5) # your long-running job goes here...
finally:
    rt.stop() # better in a try/finally block to make sure the program ends!

Features:

  • Standard library only, no external dependencies
  • start() and stop() are safe to call multiple times even if the timer has already started/stopped
  • function to be called can have positional and named arguments
  • You can change interval anytime, it will be effective after next run. Same for args, kwargs and even function!
Newmown answered 31/10, 2012 at 4:31 Comment(11)
All I need is the sleep() function. Thank you.Allophane
@JossieCalderon: The sleep() function is in the time module from Python's Standard Library, no need of any additional code to use it besides the import. But please note this is a one-time blocking call, not a repeated multi-threaded timer as the OP requested. Basically, sleep() is just a pause, not a timer.Newmown
@Newmown Yep - it only runs when it's called.Allophane
This is nice, but note that it doesn't run the job every n seconds. Instead, it runs a n-second timer, then quickly starts another n-second timer... but there is a delay before the next timer starts. So this doesn't fire precisely every n seconds; rather, it drifts, with a bit more than n seconds between jobs. A 1 second timer running a job to print the current time yields: starting... 16:07:42.017682 16:07:43.023215 16:07:44.023626 etc.Tavey
Yes, @Tavey , it does drift as there is no straightforward way to set a truly recurring timer using Python's Standard Library, so I "chained" several one-time timers to simulate it. The drift, however, was 5ms on first run and 0.4ms on the following, so it would take approximately somewhere from 200 to 2500 runs to drift a whole second, which might or might not be significant for you. For more accuracy over thousands of runs one could either calculate the next run based on the RTC or use an external tool such as cronNewmown
Thank you for this. I added self._timer.daemon = True to handle shutdowns better and moved the function call right after self._timer.start() to have it run immediately.Etymologize
what should I do If I want to stop the function after 30 mins.Calamus
@Sarthak: stop the timer you say? You could either add some logic to the called function to check elapsed time (comparing current time with a global, previously saved start time) or create another 30-minute timer, both approaches would disable the repeated timer (and also itself in case of the second timer)Newmown
I did run this timer with a interval of 0.1 seconds, thus each time a thread new gets with a new timer. Used self._timer.daemon = True and return to improve memory allocation over time . def start(self): if not self.is_running: self._timer = threading.Timer(self.interval, self._run) self._timer.daemon = True self._timer.start() self.is_running = True return #improves ability to be garbage collectedGuadiana
Note that this code makes new threads over and over, and will quite happily error out in a way that can't be caught.Circumspect
@Mike'Pomax'Kamermans: actually, it will make a single thread over and over, so at any given time you'd have at most 2 threads: the main one and the timer. And yes, handling errors between threads is tricky and out of the scope for a simple std lib class like this. But one could (and should) always handle exceptions in the called funcion (in my example, hello()Newmown
A
34

Save yourself a schizophrenic episode and use the Advanced Python scheduler:

The code is so simple:

from apscheduler.scheduler import Scheduler

sched = Scheduler()
sched.start()

def some_job():
    print "Every 10 seconds"

sched.add_interval_job(some_job, seconds = 10)

....
sched.shutdown()
Accentual answered 5/7, 2013 at 15:59 Comment(4)
First of all, the submodule is called 'schedulers', with an 's'. And there is no class Scheduler in there. Maybe BackgroundScheduler? Anyway, this answer is incomplete and does not work.Hutchens
It's been a while, I guess I pasted the code from the web user manual. The above code is now corrected (still not tested, but it comes from my own working code and I am using it constantly). PS: maybe we are looking at different versions / modules? I'm sure my line is "from apscheduler.scheduler import Scheduler" with capital S and not plural.Accentual
@MadsSkjern: I see that 2.1 branch has apscheduler.scheduler (no s) module. The current branch 3 does not.Munafo
Anyone looking at this answer now (Nov 2014) should be aware that this, while a good answer, is all wrong. The above comments discuss this. To add a job in the current version the code would read sched.add_job(some_job, 'interval', seconds = 10). Look at the documentationAdventitia
K
30
def update():
    import time
    while True:
        print 'Hello World!'
        time.sleep(5)

That'll run as a function. The while True: makes it run forever. You can always take it out of the function if you need.

Karlotte answered 3/8, 2010 at 4:48 Comment(8)
Doesn't work; it just runs forever and I cant do anything else while it is.Dayan
What other things do you want to be doing at the same time?Karl
Well this just runs in a loop. You didn't specify in the question that you'd be doing something else in the meantime so I assumed that's what you need.Karlotte
Note: This will not quite run every 5 seconds. It will run every 5 + N seconds, where N is the time it takes to execute the time between while True: and time.sleep(5). While this is negligible for print 'Hellow World!' in comparison to 5 whole seconds, it may be nontrivial for other scenarios. I should note that using threads won't quite get it to 5 seconds exactly either, but it will be closer.Haycock
I know I'm like 9 years too late, but this isn't a valid solution, he specifically said ...the program would go through whatever code I had, then once it had been 5 seconds....Tamanaha
@Karlotte Thanks for your help. This simple snippet of code saved my day.Dedra
this keeps GIL locked while the fuction "executes" the sleep command , making python not available for other tasks.Pettus
then you need to offload to this function to a thread, you can also use a subprocess as well that doesn't wait for the returnPhilibeg
M
28

Here is a simple example compatible with APScheduler 3.00+:

# note that there are many other schedulers available
from apscheduler.schedulers.background import BackgroundScheduler

sched = BackgroundScheduler()

def some_job():
    print('Every 10 seconds')

# seconds can be replaced with minutes, hours, or days
sched.add_job(some_job, 'interval', seconds=10)
sched.start()

...

sched.shutdown()

Alternatively, you can use the following. Unlike many of the alternatives, this timer will execute the desired code every n seconds exactly (irrespective of the time it takes for the code to execute). So this is a great option if you cannot afford any drift.

import time
from threading import Event, Thread

class RepeatedTimer:

    """Repeat `function` every `interval` seconds."""

    def __init__(self, interval, function, *args, **kwargs):
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.start = time.time()
        self.event = Event()
        self.thread = Thread(target=self._target)
        self.thread.start()

    def _target(self):
        while not self.event.wait(self._time):
            self.function(*self.args, **self.kwargs)

    @property
    def _time(self):
        return self.interval - ((time.time() - self.start) % self.interval)

    def stop(self):
        self.event.set()
        self.thread.join()


# start timer
timer = RepeatedTimer(10, print, 'Hello world')

# stop timer
timer.stop()
Messinger answered 10/10, 2015 at 14:14 Comment(0)
M
12

Here's a version that doesn't create a new thread every n seconds:

from threading import Event, Thread

def call_repeatedly(interval, func, *args):
    stopped = Event()
    def loop():
        while not stopped.wait(interval): # the first call is in `interval` secs
            func(*args)
    Thread(target=loop).start()    
    return stopped.set

The event is used to stop the repetitions:

cancel_future_calls = call_repeatedly(5, print, "Hello, World")
# do something else here...
cancel_future_calls() # stop future calls

See Improve current implementation of a setInterval python

Munafo answered 23/8, 2015 at 13:25 Comment(4)
It's worth noting that while this does allow something else to run while waiting for func to run again, this solution does not account for how long it takes func itself to run. So this solution waits n seconds between runs, as opposed to running every n seconds. Other solutions above at least come much closer to running every n seconds, though this solution has its advantages too.Silica
@Mike: yes. If you follow the link; you see that it is mentioned explicitly and the corresponding solution is suggested. It depends on a specific case what solution is preferable.Munafo
it would be very nice if you could explain if the print function is called in the main thread. If yes, would the do something else code be run in another thread?Pneumatic
@Pneumatic look at the code: "print" function is passed as "func" parameter to "call_repeatedly". "func" is called inside "loop" that is executed in another thread (look at Thread(target=loop)). In short: no, "print" function is not called in the main thread here.Munafo
S
1

You can start a separate thread whose sole duty is to count for 5 seconds, update the file, repeat. You wouldn't want this separate thread to interfere with your main thread.

Spina answered 3/8, 2010 at 4:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.