简体   繁体   English

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

[英]Repeating a function in a background thread every N seconds

I know this sounds a lot like this similarly-worded question , but there are differences, so bear with me. 我知道这听起来很像这个措辞相似的问题 ,但是存在差异,所以请耐心等待。

I'm trying to create a reusable "Timer" class which calls a specified callback every N seconds, until you call stop . 我正在尝试创建一个可重复使用的“Timer”类,它每隔N秒调用一次指定的回调,直到你调用stop As inspiration, I used the link above, with a built-in event wrapped in a stop method. 作为灵感,我使用了上面的链接,内置事件包含在stop方法中。 Here's how the basic class looks: 以下是基本类的外观:

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

# Mostly inspired by https://stackoverflow.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()

Looks good, even includes time.sleep(0) based on this question . 看起来不错,甚至包括time.sleep(0)基于这个问题

It doesn't do what I thought; 它不符合我的想法; the call to start never seems to return or yield, ever. 在通话start似乎永远不会返回或屈服,永远。 Consider this use-case: 考虑这个用例:

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()

The call to r1.start never terminates. r1.start的调用永远不会终止。 It continues on forever. 它会永远持续下去。 The output on the console, after four seconds, is: 四秒钟后,控制台上的输出为:

R1 R1

R1 R1

R1 R1

R1 R1

This prompted me to introduce the time.sleep(0) call, although that doesn't seem to do anything. 这促使我引入time.sleep(0)调用,虽然这似乎没有做任何事情。

I also tried with and without self.setDaemon(True) , but that also seems to have no effect. 我也尝试过使用和不使用self.setDaemon(True) ,但这似乎也没有效果。

I also tried converting this into two classes: one with just the event wrappers (a StoppableTimer class), and another that simply creates and recreates the StoppableTimer in the callback, but that doesn't work either. 我也尝试将它转换为两个类:一个只有事件包装器(一个StoppableTimer类),另一个只是在回调中创建并重新创建StoppableTimer ,但这也不起作用。 Here's what it looks like: 这是它的样子:

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()

I'm completely at a loss on how to make this work. 我完全不知道如何做这项工作。 I'm also mostly a beginner to Python, so please add sufficient explanation to your answer so I can grasp what the fundamental issue is. 我也是Python的初学者,所以请为你的答案添加足够的解释,这样我就能掌握基本问题。

I also read a bit about the Global Interpreter Lock on SO , but I don't understand how that could be an issue. 我还读了一下关于SO全球解释器锁 ,但我不明白这可能是一个问题。

For reference, I'm running Python 3.6.3 on Ubuntu 17.10 作为参考,我在Ubuntu 17.10上运行Python 3.6.3

Short answer : 简短回答:

Don't override start() . 不要覆盖start() Override run() instead. 改为覆盖run()

Long answer because you're asking for details : 答案很长,因为你要求详细信息:

With the class definition in your first snippet, you've created a class which inherits from Thread , however you've overriden the start() method supposed to start your thread by a new method which is looping until the stop_event is set, that is to say, the method supposed to actually start your thread doesn't do this anymore. 使用第一个片段中的类定义,您已经创建了一个继承自Thread的类,但是您已经覆盖了应该通过循环的新方法启动线程的start()方法,直到设置了stop_event ,即要说,实际启动你的线程的方法不再这样做了。

So, when you try to start your thread, you actually run the loop calling your callback function in your current and only thread . 因此,当您尝试启动线程时,实际上是在当前和唯一的线程中运行调用回调函数的循环。 And since it's an infinite loop, your second "thread" is not started, and you have no way to "stop" it. 由于它是一个无限循环,你的第二个“线程”没有启动,你无法“停止”它。

You mustn't override start (well not in this way). 你不能覆盖start (不是这样)。 Instead, override the run method. 而是覆盖run方法。 This is the method that will be run by your thread when you start it. 这是启动它时线程将运行的方法。

Also, you should do super().__init__() instead of Thread.__init__(self) . 此外,你应该做super().__init__()而不是Thread.__init__(self) The first one is the proper way to call an inherited method in Python. 第一个是在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()

And with the functions you've defined you can do : 使用您定义的功能,您可以执行以下操作:

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 . 这是Thread的相关文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM