Python threading.timer-每'n'秒重复一次函数


94

我想每0.5秒触发一次功能,并且能够启动,停止和重置计时器。我不太了解Python线程的工作方式,并且在使用python计时器时遇到了困难。

但是,RuntimeError: threads can only be started once当我执行threading.timer.start()两次时,我会不断得到帮助。有没有解决的办法?我尝试threading.timer.cancel()在每次开始之前申请。

伪代码:

t=threading.timer(0.5,function)
while True:
    t.cancel()
    t.start()

Answers:


112

最好的方法是一次启动计时器线程。在计时器线程中,您需要编写以下代码

class MyThread(Thread):
    def __init__(self, event):
        Thread.__init__(self)
        self.stopped = event

    def run(self):
        while not self.stopped.wait(0.5):
            print("my thread")
            # call a function

然后,在启动计时器的代码中,您可以set停止事件来停止计时器。

stopFlag = Event()
thread = MyThread(stopFlag)
thread.start()
# this will stop the timer
stopFlag.set()

4
然后它将完成睡眠,然后停止。没有办法在Python中强制挂起线程。这是python开发人员做出的设计决定。但是,最终结果将是相同的。您的线程仍会运行一小会儿(睡眠),但不会执行您的功能。
汉斯,然后

13
好吧,实际上,如果您希望能够立即停止计时器线程,只需使用a threading.Eventwait不是sleep。然后,要唤醒它,只需设置事件即可。您甚至不需要self.stoppedthen,因为您只需检查事件标志即可。
nneonneo 2012年

3
该事件将严格用于中断计时器线程。通常,event.wait这只是超时并像睡眠一样,但是如果您想停止(或以其他方式中断线程),则可以设置线程的事件,它会立即唤醒。
nneonneo 2012年

2
我已经更新了使用event.wait()的答案。感谢您的建议。
汉斯,然后

1
只是一个问题,那之后如何重新启动线程?电话thread.start()给了我threads can only be started once
Motassem MK

33

等效于setInterval在python中

import threading

def setInterval(interval):
    def decorator(function):
        def wrapper(*args, **kwargs):
            stopped = threading.Event()

            def loop(): # executed in another thread
                while not stopped.wait(interval): # until stopped
                    function(*args, **kwargs)

            t = threading.Thread(target=loop)
            t.daemon = True # stop if the program exits
            t.start()
            return stopped
        return wrapper
    return decorator

用法:

@setInterval(.5)
def function():
    "..."

stop = function() # start timer, the first call is in .5 seconds
stop.set() # stop the loop
stop = function() # start new timer
# ...
stop.set() 

或者这是相同的功能,但作为独立功能而不是装饰器

cancel_future_calls = call_repeatedly(60, print, "Hello, World")
# ...
cancel_future_calls() 

这是不使用thread的方法


使用装饰器时如何更改间隔?说我想在运行时将.5s更改为1秒或其他?
lightxx

@lightxx:只需使用即可@setInterval(1)
2013年

嗯。所以我有点慢,或者您误解了我。我的意思是在运行时。我知道我可以随时在源代码中更改装饰器。例如,我有三个函数,每个函数都用@setInterval(n)装饰。现在在运行时,我想更改功能2的间隔,但不要更改功能1和3。
lightxx 2013年

@lightxx:您可以使用其他接口,例如stop = repeat(every=second, call=your_function); ...; stop()
jfs


31

使用计时器线程-

from threading import Timer,Thread,Event


class perpetualTimer():

   def __init__(self,t,hFunction):
      self.t=t
      self.hFunction = hFunction
      self.thread = Timer(self.t,self.handle_function)

   def handle_function(self):
      self.hFunction()
      self.thread = Timer(self.t,self.handle_function)
      self.thread.start()

   def start(self):
      self.thread.start()

   def cancel(self):
      self.thread.cancel()

def printer():
    print 'ipsem lorem'

t = perpetualTimer(5,printer)
t.start()

这可以通过停止 t.cancel()


3
我相信这段代码在cancel方法中存在错误。调用此方法时,线程要么是1)未运行,要么是2)正在运行。在1)中,我们正在等待运行该功能,因此cancel可以正常工作。在2)我们当前正在运行,因此取消将不会对当前执行产生影响。此外,当前执行会重新安排自身的时间,因此不会对将来产生影响。
Rich Episcopo

1
每次计时器耗尽时,此代码都会创建一个新线程。与接受的答案相比,这是巨大的浪费。
阿德里安W

由于上述原因,应避免使用此解决方案:每次都会创建一个新线程
皮钦亚

17

改善Hans Then的答案,我们可以将Timer函数子类化。以下是我们完整的 “重复计时器”代码,它可以用作具有所有相同参数的threading.Timer的替代品:

from threading import Timer

class RepeatTimer(Timer):
    def run(self):
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)

用法示例:

def dummyfn(msg="foo"):
    print(msg)

timer = RepeatTimer(1, dummyfn)
timer.start()
time.sleep(5)
timer.cancel()

产生以下输出:

foo
foo
foo
foo

timer = RepeatTimer(1, dummyfn, args=("bar",))
timer.start()
time.sleep(5)
timer.cancel()

产生

bar
bar
bar
bar

这种方法会允许我启动/取消/启动/取消计时器线程吗?
Paul Knopf

1
不会。虽然这种方法允许您使用普通计时器执行任何操作,但不能使用普通计时器执行此操作。由于启动/取消与基础线程相关,因此,如果您尝试.start()一个先前已执行.cancel()的线程,则将获得异常RuntimeError: threads can only be started once
right2clicky

真的很优雅的解决方案!奇怪的是,他们不仅仅包括执行此操作的类。
罗杰·达尔

这个解决方案非常令人印象深刻,但是我仅通过阅读Python3线程Timer接口文档便难以理解它的设计方式。答案似乎建立在通过进入threading.py模块本身来了解实现的基础上。
6

14

为了按照要求的OP使用Timer提供正确的答案,我将改进swapnil jariwala的答案

from threading import Timer


class InfiniteTimer():
    """A Timer class that does not stop, unless you want it to."""

    def __init__(self, seconds, target):
        self._should_continue = False
        self.is_running = False
        self.seconds = seconds
        self.target = target
        self.thread = None

    def _handle_target(self):
        self.is_running = True
        self.target()
        self.is_running = False
        self._start_timer()

    def _start_timer(self):
        if self._should_continue: # Code could have been running when cancel was called.
            self.thread = Timer(self.seconds, self._handle_target)
            self.thread.start()

    def start(self):
        if not self._should_continue and not self.is_running:
            self._should_continue = True
            self._start_timer()
        else:
            print("Timer already started or running, please wait if you're restarting.")

    def cancel(self):
        if self.thread is not None:
            self._should_continue = False # Just in case thread is running and cancel fails.
            self.thread.cancel()
        else:
            print("Timer never started or failed to initialize.")


def tick():
    print('ipsem lorem')

# Example Usage
t = InfiniteTimer(0.5, tick)
t.start()

3

我已经更改了swapnil-jariwala代码中的一些代码,使控制台时钟变小了。

from threading import Timer, Thread, Event
from datetime import datetime

class PT():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

def printer():
    tempo = datetime.today()
    h,m,s = tempo.hour, tempo.minute, tempo.second
    print(f"{h}:{m}:{s}")


t = PT(1, printer)
t.start()

输出值

>>> 11:39:11
11:39:12
11:39:13
11:39:14
11:39:15
11:39:16
...

带tkinter图形界面的计时器

此代码将时钟计时器与tkinter放在一个小窗口中

from threading import Timer, Thread, Event
from datetime import datetime
import tkinter as tk

app = tk.Tk()
lab = tk.Label(app, text="Timer will start in a sec")
lab.pack()


class perpetualTimer():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

    def cancel(self):
        self.thread.cancel()


def printer():
    tempo = datetime.today()
    clock = "{}:{}:{}".format(tempo.hour, tempo.minute, tempo.second)
    try:
        lab['text'] = clock
    except RuntimeError:
        exit()


t = perpetualTimer(1, printer)
t.start()
app.mainloop()

抽认卡游戏的示例(一种)

from threading import Timer, Thread, Event
from datetime import datetime


class perpetualTimer():

    def __init__(self, t, hFunction):
        self.t = t
        self.hFunction = hFunction
        self.thread = Timer(self.t, self.handle_function)

    def handle_function(self):
        self.hFunction()
        self.thread = Timer(self.t, self.handle_function)
        self.thread.start()

    def start(self):
        self.thread.start()

    def cancel(self):
        self.thread.cancel()


x = datetime.today()
start = x.second


def printer():
    global questions, counter, start
    x = datetime.today()
    tempo = x.second
    if tempo - 3 > start:
        show_ans()
    #print("\n{}:{}:{}".format(tempo.hour, tempo.minute, tempo.second), end="")
    print()
    print("-" + questions[counter])
    counter += 1
    if counter == len(answers):
        counter = 0


def show_ans():
    global answers, c2
    print("It is {}".format(answers[c2]))
    c2 += 1
    if c2 == len(answers):
        c2 = 0


questions = ["What is the capital of Italy?",
             "What is the capital of France?",
             "What is the capital of England?",
             "What is the capital of Spain?"]

answers = "Rome", "Paris", "London", "Madrid"

counter = 0
c2 = 0
print("Get ready to answer")
t = perpetualTimer(3, printer)
t.start()

输出:

Get ready to answer
>>> 
-What is the capital of Italy?
It is Rome

-What is the capital of France?
It is Paris

-What is the capital of England?
...

如果hFunction处于阻塞状态,这是否会增加后续启动时间的延迟?也许您可以交换行,以便handle_function首先启动计时器,然后调用hFunction?
小胡子

2

我必须为此做一个项目。我最终要做的是为该函数启动一个单独的线程

t = threading.Thread(target =heartbeat, args=(worker,))
t.start()

****心跳是我的职责,工人是我的论点之一****

我的心跳功能的内部:

def heartbeat(worker):

    while True:
        time.sleep(5)
        #all of my code

因此,当我启动线程时,该函数将反复等待5秒钟,运行所有代码,并无限期地执行该操作。如果要终止进程,则终止线程。



1
from threading import Timer
def TaskManager():
    #do stuff
    t = Timer( 1, TaskManager )
    t.start()

TaskManager()

这是一个小样本,它将有助于更好地了解其运行方式。函数taskManager()最后创建对其自身的延迟函数调用。

尝试更改“ dalay”变量,您将看到差异

from threading import Timer, _sleep

# ------------------------------------------
DATA = []
dalay = 0.25 # sec
counter = 0
allow_run = True
FIFO = True

def taskManager():

    global counter, DATA, delay, allow_run
    counter += 1

    if len(DATA) > 0:
        if FIFO:
            print("["+str(counter)+"] new data: ["+str(DATA.pop(0))+"]")
        else:
            print("["+str(counter)+"] new data: ["+str(DATA.pop())+"]")

    else:
        print("["+str(counter)+"] no data")

    if allow_run:
        #delayed method/function call to it self
        t = Timer( dalay, taskManager )
        t.start()

    else:
        print(" END task-manager: disabled")

# ------------------------------------------
def main():

    DATA.append("data from main(): 0")
    _sleep(2)
    DATA.append("data from main(): 1")
    _sleep(2)


# ------------------------------------------
print(" START task-manager:")
taskManager()

_sleep(2)
DATA.append("first data")

_sleep(2)
DATA.append("second data")

print(" START main():")
main()
print(" END main():")

_sleep(2)
DATA.append("last data")

allow_run = False

1
您还能告诉我更多有关此方法的原因吗?
6

您的示例有点混乱,第一个代码块就是您需要说的。
Partack '19

1

我喜欢right2clicky的答案,尤其是因为它不需要拆除Thread并在每次Timer计时时创建一个新线程。此外,创建带有定期被调用的计时器回调的类很容易覆盖。那是我的正常用例:

class MyClass(RepeatTimer):
    def __init__(self, period):
        super().__init__(period, self.on_timer)

    def on_timer(self):
        print("Tick")


if __name__ == "__main__":
    mc = MyClass(1)
    mc.start()
    time.sleep(5)
    mc.cancel()

1

这是使用函数而不是类的替代实现。受到@Andrew Wilkins的启发。

因为等待比睡眠更准确(将函数运行时考虑在内):

import threading

PING_ON = threading.Event()

def ping():
  while not PING_ON.wait(1):
    print("my thread %s" % str(threading.current_thread().ident))

t = threading.Thread(target=ping)
t.start()

sleep(5)
PING_ON.set()

1

我想出了SingleTon类的另一个解决方案。请告诉我这里是否有内存泄漏。

import time,threading

class Singleton:
  __instance = None
  sleepTime = 1
  executeThread = False

  def __init__(self):
     if Singleton.__instance != None:
        raise Exception("This class is a singleton!")
     else:
        Singleton.__instance = self

  @staticmethod
  def getInstance():
     if Singleton.__instance == None:
        Singleton()
     return Singleton.__instance


  def startThread(self):
     self.executeThread = True
     self.threadNew = threading.Thread(target=self.foo_target)
     self.threadNew.start()
     print('doing other things...')


  def stopThread(self):
     print("Killing Thread ")
     self.executeThread = False
     self.threadNew.join()
     print(self.threadNew)


  def foo(self):
     print("Hello in " + str(self.sleepTime) + " seconds")


  def foo_target(self):
     while self.executeThread:
        self.foo()
        print(self.threadNew)
        time.sleep(self.sleepTime)

        if not self.executeThread:
           break


sClass = Singleton()
sClass.startThread()
time.sleep(5)
sClass.getInstance().stopThread()

sClass.getInstance().sleepTime = 2
sClass.startThread()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.