每 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

【问题讨论】:

您似乎已经覆盖了Threadstart 方法,您可以想象,这不是一个好主意,尤其是如果您从未调用实际的 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 秒在后台线程中重复一个函数的主要内容,如果未能解决你的问题,请参考以下文章

每 10-15 秒在后台使用 GPS 是不是是一个好习惯?

如何在后台线程上创建 NSTimer?

如何在后台线程中按顺序运行任务[重复]

在后台和主线程中使用托管对象上下文

在objective-c中,后台线程中的重复方法是不是正确?

在 UI 或后台线程上写入文件 [重复]