每 N 秒在后台线程中重复一个函数
Posted
技术标签:
【中文标题】每 N 秒在后台线程中重复一个函数【英文标题】:Repeating a function in a background thread every N seconds 【发布时间】:2018-04-27 17:55:36 【问题描述】:我知道这听起来很像this similarly-worded question,但也有区别,请耐心等待。
我正在尝试创建一个可重用的“计时器”类,它每 N 秒调用一次指定的回调,直到您调用 stop
。作为灵感,我使用了上面的链接,并在 stop
方法中包含了一个内置事件。下面是基本类的外观:
import time
import threading
from threading import Thread
from threading import Event
# Mostly inspired by https://***.com/questions/12435211/python-threading-timer-repeat-function-every-n-seconds
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()
看起来不错,甚至包括基于this question 的time.sleep(0)
。
这和我想的不一样;对start
的调用似乎永远不会返回或屈服。考虑这个用例:
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)
,但这似乎也没有效果。
我还尝试将其转换为两个类:一个仅包含事件包装器(StoppableTimer
类),另一个仅在回调中创建和重新创建 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 的信息,但我不明白这怎么会是个问题。
作为参考,我在 Ubuntu 17.10 上运行 Python 3.6.3
【问题讨论】:
您似乎已经覆盖了Thread
的start
方法,您可以想象,这不是一个好主意,尤其是如果您从未调用实际的 Thread.start 时
@pvg 这可能是我在这里对 Python 的缺乏经验。我从字面上复制粘贴了链接线程中的代码,并对其进行了修改以使事件成为成员变量而不是外部性。我将做一个注释以编辑链接问题中的代码示例。
你没有,我检查了:)。您已经重写了代码,使其无法正常工作。有人会注意到它在原始答案中不起作用并指出来。
@pvg 我复制粘贴了它,但在某些时候,我将run
重命名为start
(没有意识到它的含义)。这可能解释了反对票...
【参考方案1】:
简答:
不要覆盖start()
。改为覆盖 run()
。
回答很长,因为您要询问详细信息:
使用第一个 sn-p 中的类定义,您创建了一个继承自 Thread
的类,但是您已经覆盖了应该通过一个新方法启动线程的 start()
方法,该方法一直循环到stop_event
已设置,也就是说,应该实际启动线程的方法不再这样做了。
因此,当您尝试启动线程时,实际上是在在当前且唯一的线程中运行调用回调函数的循环。而且由于它是一个无限循环,因此您的第二个“线程”没有启动,您无法“停止”它。
你不能覆盖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
.
【讨论】:
避免这一切的更明智的方法是“不要从线程继承”。线程和计时器之间没有明显的“是”关系。计时器可能使用线程实现,它可能包含一个线程但它不是线程。 @pvg 我不知道那会是什么样子。随意创建一个新答案并用一些代码解释/说明您的建议。 感谢工作代码和详细解释 (+1)。我将等待,看看是否有人可以建议如何在不继承Thread
的情况下执行此操作,如果几天后没有任何反应,我会接受这个答案。
@nightblade9 我不认为我为您编写代码以解决不同的问题有什么收获。你应该接受这个答案——它回答了你的问题,它本身实际上主要是一个“错字或不再可重现”类型的问题,不太可能帮助其他人。您应该 做的是按照建议阅读有关 Thread 的文档 - 它立即涵盖了子类化和委托案例,并明确告诉您不要覆盖 run
以外的方法。您还可以查看涵盖这两种情况的线程教程,例如 pymotw.com/2/threading。
@nightblade9 同样,当您熟悉 Python 时,您可能会对 asyncio 感兴趣。例如:***.com/questions/37512182/…以上是关于每 N 秒在后台线程中重复一个函数的主要内容,如果未能解决你的问题,请参考以下文章