每 N 秒在后台线程中重复一个函数

Repeating a function in a background thread every N seconds

我知道这听起来很像 this similarly-worded question,但还是有区别的,所以请耐心等待。

我正在尝试创建一个可重复使用的“计时器”class,它每 N 秒调用一次指定的回调,直到您调用 stop。作为灵感,我使用了上面的 link,内置事件包装在 stop 方法中。这是基本 class 的样子:

import time
import threading
from threading import Thread
from threading import Event

# Mostly inspired by 
class RepeatingTimer(Thread):
    def __init__(self, interval_seconds, callback):
        Thread.__init__(self)
        self.stop_event = Event()
        self.interval_seconds = interval_seconds
        self.callback = callback
        self.setDaemon(True)

    def start(self):
        while not self.stop_event.wait(self.interval_seconds):
            self.callback()
            time.sleep(0) # doesn't seem to do anything
    
    def stop(self):
        self.stop_event.set()

看起来不错,甚至包括 time.sleep(0) 基于 this question

它和我想的不一样;对 start 的调用似乎永远不会 return 或屈服。考虑这个用例:

def print_status(message):
  print(message)

def print_r1():
  print_status("R1")

def print_r2():
  print_status("R2")

r1 = RepeatingTimer(1, print_r1)
r2 = RepeatingTimer(0.5, print_r2)

r1.start()
r2.start()

r1.start 的调用永远不会终止。它永远持续下去。四秒后,控制台上的输出为:

R1

R1

R1

R1

这促使我引入 time.sleep(0) 调用,尽管它似乎没有任何作用。

我也尝试过使用和不使用 self.setDaemon(True),但这似乎也没有效果。

我还尝试将其转换为两个 classes:一个只有事件包装器(一个 StoppableTimer class),另一个只是创建和重新创建 StoppableTimer 在回调中,但这也不起作用。这是它的样子:

class StoppableTimer(Thread):
    def __init__(self, interval_seconds, callback):
        Thread.__init__(self)
        self.stop_event = Event()
        self.interval_seconds = interval_seconds
        self.callback = callback
        self.setDaemon(True)

    def start(self):
        time.sleep(self.interval_seconds)
        self.callback()
    
    def stop(self):
        self.stop_event.set()


class RepeatingTimer:
    def __init__(self, interval_seconds, callback):
        self.interval_seconds = interval_seconds
        self.callback = callback
        self.timer = StoppableTimer(interval_seconds, self.refresh_timer)

    def start(self):
        self.timer.start()

    def stop(self):
        self.timer.stop()

    def refresh_timer(self):
        self.stop()
        self.callback()
        self.timer = StoppableTimer(self.interval_seconds, self.refresh_timer)
        self.timer.start()

我完全不知道如何进行这项工作。我也是 Python 的初学者,所以请在您的回答中添加充分的解释,以便我了解根本问题是什么。

我也阅读了一些关于 Global Interpreter Lock on SO 的内容,但我不明白这怎么可能是个问题。

作为参考,我是 运行 Python 3.6.3 Ubuntu 17.10

简答:

不要覆盖 start()。改写 run()

答案很长,因为您要询问详细信息:

使用第一个代码段中的 class 定义,您创建了一个继承自 Thread 的 class,但是您已经覆盖了假定的 start() 方法通过一个循环直到设置 stop_event 的新方法启动你的线程,也就是说,应该实际启动你的线程的方法不再执行此操作。

因此,当您尝试启动您的线程时,您实际上 运行 循环调用您的回调函数 在您当前且唯一的线程 中。而且由于是无限循环,你的第二个"thread"没有启动,你也没办法"stop"了。

您不能覆盖 start(当然不能以这种方式)。相反,覆盖 run 方法。这是您的线程在启动时将 运行 执行的方法。

此外,您应该 super().__init__() 而不是 Thread.__init__(self)。第一个是在 Python.

中调用继承方法的正确方法
class RepeatingTimer(Thread):
    def __init__(self, interval_seconds, callback):
        super().__init__()
        self.stop_event = Event()
        self.interval_seconds = interval_seconds
        self.callback = callback

    def run(self):
        while not self.stop_event.wait(self.interval_seconds):
            self.callback()

    def stop(self):
        self.stop_event.set()

使用您定义的函数,您可以执行以下操作:

r1 = RepeatingTimer(1, print_r1)
r2 = RepeatingTimer(0.5, print_r2)

r1.start()
r2.start()
time.sleep(4)
r1.stop()
r2.stop()

Here is the relevant documentation for Thread.