5

I am trying to implement a heartbeat call that works in the background. How do I create a threaded on interval call of say every 30 seconds, which calls the following function:

self.mqConn.heartbeat_tick()

Also how would I stop this thread?

Many thanks.

TheBear
  • 827
  • 2
  • 10
  • 21

4 Answers4

9

Use a thread containing a loop

from threading import Thread
import time

def background_task():
    while not background_task.cancelled:
        self.mqConn.heartbeat_tick()
        time.sleep(30)
background_task.cancelled = False

t = Thread(target=background_task)
t.start()

background_task.cancelled = True

Alternatively, you could subclass timer, to make cancelling easy:

from threading import Timer

class RepeatingTimer(Timer):
    def run(self):
        while not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
            self.finished.wait(self.interval)


t = RepeatingTimer(30.0, self.mqConn.heartbeat_tick)
t.start() # every 30 seconds, call heartbeat_tick

# later
t.cancel() # cancels execution
Eric
  • 95,302
  • 53
  • 242
  • 374
  • 1
    It may be important to mention that this *won't* execute every 30 seconds if `heartbeat_tick()` takes a significant amount of time – loopbackbee Apr 21 '15 at 10:38
  • Yep, this will be at least 30 seconds every time. At most 30 seconds is a fair bit harder. – Eric Apr 21 '15 at 10:38
  • 1
    If your foreground function was CPU bound, would the background thread be prevented from ever executing by the GIL? – Jeff Widman Feb 08 '17 at 02:37
  • @JeffWidman: CPU-bound and "doesn't release the GIL" are not always the same thing. Yes, if the foreground thread held onto the GIL for a minute at a time, then I imagine the background thread would be delayed. – Eric Feb 08 '17 at 11:44
  • Thanks @Eric, I did a little more reading on David Beazley's lectures re: the GIL and makes more sense. – Jeff Widman Feb 09 '17 at 11:13
3

Or you could use the Timer class in the threading module:

from threading import Timer

def hello():
    print "hello, world"

t = Timer(30.0, hello)
t.start() # after 30 seconds, "hello, world" will be printed
t.cancel() # cancels execution, this only works before the 30 seconds is elapsed

This will not start every x seconds, rather it delays the thread for execution in x seconds. But you can still put that in a loop and use t.is_alive() to see its status.

Joe
  • 2,496
  • 1
  • 22
  • 30
3

A quick followup to Eric's answer: you can't subclass Timer in python 2, since it's actually a light function wrapper around the true class: _Timer. If you do you'll get the issue that pops up in this post.

Using _Timer instead fixes it:

from threading import _Timer

class RepeatingTimer(_Timer):
    def run(self):
        while not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
            self.finished.wait(self.interval)


t = RepeatingTimer(30.0, self.mqConn.heartbeat_tick)
t.start() # every 30 seconds, call heartbeat_tick

# later
t.cancel() # cancels execution
Jaron
  • 117
  • 8
Tom
  • 31
  • 1
  • I wouldn't be surprised if `_Timer` was subject to breaking changes through versions, while `Timer` is not (because of the leading prefix "privacy"). I had no issues subclassing `Timer` in 3.7, and the documentation says it's a subclass of `Thread`. – Far_Eggs Oct 19 '18 at 20:32
  • @Far_Eggs you're likely correct - good comment. I should've been specific that this was with respect to 2.7. – Tom Oct 20 '18 at 22:39
2

One way to do this would be to use the circuits application framework like this:

from circuits import Component, Event, Timer


class App(Component):

    def init(self, mqConn):
        self.mqConn = mqConn
        Timer(30, Event.create("heartbeat"), persist=True).register(self)

    def heartbeat(self):
        self.mqConn.heartbeat_tick()


App().run()

Note: I'm the author of circuits :)

This is just a basic idea and structure -- You would need to adapt this to suit your exact application and requirements!

James Mills
  • 18,669
  • 3
  • 49
  • 62