views:

338

answers:

2

Hello,

I have recently started learning Python and part of the simple app I am making includes a timer with a hh:mm:ss display running in its own thread.

Looking around the web I found two ways of implementing this:

  1. Using sched.scheduler
  2. Using threading.Timer

The way I did it looks similar for both implementations:

sched:

def tick(self, display, alarm_time):

    # Schedule this function to run every minute
    s = sched.scheduler(time.time, time.sleep)
    s.enter(1, 1, self.tick, ([display, alarm_time]))

    # Update the time
    self.updateTime(display)

Timer:

def tick(self, display):

    # Schedule this function to run every second
    t = Timer(1, self.tick, (display,alarm_time))
    t.start()

    # Update the time
    self.updateTime(display)
  1. Works fine with regards to ticking correctly, but generates the following error after a few minutes: RuntimeError: maximum recursion depth exceeded. I know you can increase the max recursion level manually, but surely this should not be necessary here?

  2. No error, but occasionally the seconds will skip, or tick irregularly.

Can someone please point me in the right direction as to how to do this correctly? Thank you.

+2  A: 

A Timer is a one-shot event. It cannot be made to loop this way.

Using a Timer to call a function which then creates another Timer which calls a function that creates a Timer which calls a function which creates a Timer, ..., must reach the recursion limit.

You don't mention your OS, but the "skipping" or "ticking irregularly" is for two reasons.

  1. You computer is busy and "1 second" means "pretty close to 1 second, depending on what else is going on"

  2. If you start your timer at 0.9999 seconds, and wait 1 second, you might be at 1.9999 (rounds down to 1) or 2.00000. It may appear to duplicate a time or skip a time. Your computer's internal hardware clock is very accurate, and rounding things off to the nearest second will (always) lead to the remote possibility of duplicates or skips.

Use sched correctly. http://docs.python.org/library/sched.html#module-sched

Your code snippet makes no sense for sched, either. You do not need to create a new scheduler object. You only need to create a new event.

Read http://docs.python.org/library/sched.html#sched.scheduler.enter on creating a new event for an existing scheduler instance.

S.Lott
Thanks for your answer. I have clarified the question to include my sched implementation and the error, but I am guessing the reason is the same you have given for Timer.However if sched just schedules a single event at a later date, I still do not see how to avoid the recursion limit :(
very clear explaination
DrFalk3n
+1  A: 

Here's how to make a one-shot into a periodic event, e.g. with sched: if the function must make its own scheduler and be the only thing running on its thread:

def tick(self, display, alarm_time, scheduler=None):
  # make a new scheduler only once & schedule this function immediately
  if scheduler is None:
    scheduler = sched.scheduler(time.time, time.sleep)
    scheduler.enter(0, 1, self.tick, ([display, alarm_time, scheduler]))
    scheduler.run()

  # reschedule this function to run again in a minute
  scheduler.enter(1, 1, self.tick, (display, alarm_time, scheduler]))

  # do whatever actual work this function requires, e.g.:
  self.updateTime(display)

If other events must also be scheduled in the same thread then the scheduler must be made and owned "elsewhere" -- the if part above can get refactored into another method, e.g.:

def scheduleperiodic(self, method, *args):
  self.scheduler = sched.scheduler(time.time, time.sleep)
  self.scheduler.enter(0, 1, method, args)
  # whatever else needs to be scheduled at start, if any, can go here
  self.scheduler.run()

def tick(self, display, alarm_time):
  # reschedule this function to run again in a minute
  self.scheduler.enter(60, 1, self.tick, (display, alarm_time))

  # do whatever actual work this function requires, e.g.:
  self.updateTime(display)

Again, of course and as always with sched, while the scheduler is running, it (and the scheduled event callbacks) will "take over" the thread in question (so you'll need to hive off a separate thread for it if you need other things to be happening at the same time).

If you need to use this kind of idiom in many functions it could be refactored into a decorator, but that would somewhat mask the underlying simplicity of the idiom, so I prefer this simple, overt use. BTW, note that time.time and time.sleep use seconds, not minutes, as their unit of time, so you need 60, not one, to indicate "a minute from now";-).

Alex Martelli
I kept the suggestion for a decorator
DrFalk3n