Python sched.scheduler 超过最大递归深度

5 投票
2 回答
3238 浏览
提问于 2025-04-15 14:12

我最近开始学习Python,正在制作一个简单的应用程序,其中有一个计时器,显示格式是hh:mm:ss,并且它在自己的线程中运行。

在网上查找时,我发现了两种实现这个计时器的方法:

  1. 使用sched.scheduler
  2. 使用threading.Timer

我做的这两种实现方式看起来差不多:

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. 第一种方法在计时上运行得很好,但过了几分钟后会出现一个错误:RuntimeError: maximum recursion depth exceeded。我知道可以手动增加最大递归深度,但这在这里真的有必要吗?

  2. 第二种方法没有错误,但有时秒数会跳过,或者计时不规律。

有人能帮我指点一下,怎么才能正确实现这个功能吗?谢谢。

2 个回答

3

定时器是一种一次性事件。它不能像这样循环使用。

如果你用一个定时器去调用一个函数,然后这个函数又创建另一个定时器,再调用一个函数,然后再创建定时器,这样一直循环下去,最终会达到递归的限制。

你没有提到你的操作系统,但“跳过”或“滴答声不规律”的原因有两个。

  1. 你的电脑可能很忙,“1秒钟”其实是“差不多1秒钟,具体要看其他事情的进展情况”。

  2. 如果你在0.9999秒时启动定时器,等1秒钟后,可能会显示为1.9999(向下取整为1)或者2.00000。这可能会让你觉得时间重复了或者跳过了。你电脑内部的硬件时钟非常准确,但四舍五入到最近的秒数总是有可能导致时间重复或跳过。

正确使用调度器。http://docs.python.org/library/sched.html#module-sched

你的代码片段对于调度器来说也没有意义。你不需要创建一个新的调度器对象,只需要创建一个新的事件

查看http://docs.python.org/library/sched.html#sched.scheduler.enter,了解如何为现有的调度器实例创建一个新事件。

6

这里讲的是如何把一次性事件变成定期事件,比如使用 sched。如果这个函数需要自己创建一个调度器,并且是唯一在这个线程上运行的东西:

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 部分可以改成另一个方法,比如:

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)

当然,使用 sched 时,当调度器在运行的时候,它(以及调度的事件回调)会“占用”这个线程(所以如果你需要同时做其他事情,就得为它单独开一个线程)。

如果你在很多函数中都需要用到这种方式,可以把它改成一个装饰器,但这样会稍微掩盖这种用法的简单性,所以我更喜欢这种简单、直接的用法。顺便提一下,time.timetime.sleep 使用的是秒,而不是分钟,所以你需要用60,而不是1,来表示“从现在起一分钟”;-)。

撰写回答