Python threading.timer - Funktion alle 'n' Sekunden wiederholen

94

Ich möchte alle 0,5 Sekunden eine Funktion auslösen und den Timer starten und stoppen und zurücksetzen können. Ich weiß nicht genau, wie Python-Threads funktionieren, und habe Probleme mit dem Python-Timer.

Ich bekomme jedoch immer wieder, RuntimeError: threads can only be started oncewenn ich threading.timer.start()zweimal ausführe . Gibt es dafür eine Lösung? Ich habe versucht, mich threading.timer.cancel()vor jedem Start zu bewerben .

Pseudocode:

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

Antworten:

111

Der beste Weg ist, den Timer-Thread einmal zu starten. In Ihrem Timer-Thread würden Sie Folgendes codieren

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

In dem Code, der den Timer gestartet hat, können Sie dann setdas gestoppte Ereignis verwenden, um den Timer zu stoppen.

stopFlag = Event()
thread = MyThread(stopFlag)
thread.start()
# this will stop the timer
stopFlag.set()
Hans Dann
quelle
4
Dann wird es seinen Schlaf beenden und danach aufhören. Es gibt keine Möglichkeit , einen Thread in Python zwangsweise anzuhalten. Dies ist eine Designentscheidung der Python-Entwickler. Das Nettoergebnis wird jedoch das gleiche sein. Ihr Thread läuft noch eine kurze Zeit (Sleep), führt jedoch Ihre Funktion nicht aus.
Hans dann
13
Wenn Sie den Timer-Thread sofort stoppen möchten, verwenden Sie einfach ein threading.Eventund waitanstelle von sleep. Um es aufzuwecken, stellen Sie einfach das Ereignis ein. Sie brauchen das self.stoppeddann nicht einmal, weil Sie nur das Ereignisflag überprüfen.
Nneonneo
3
Das Ereignis wird ausschließlich verwendet, um den Timer-Thread zu unterbrechen. Normalerweise event.waitwürde das nur eine Zeitüberschreitung verursachen und sich wie ein Schlaf verhalten, aber wenn Sie den Thread stoppen (oder auf andere Weise unterbrechen) möchten, würden Sie das Ereignis des Threads festlegen und es würde sofort aufwachen.
Nneonneo
2
Ich habe meine Antwort aktualisiert, um event.wait () zu verwenden. Danke für die Vorschläge.
Hans Dann
1
Nur eine Frage, wie kann ich den Thread danach neu starten? Anruf thread.start()gibt mirthreads can only be started once
Motassem MK
32

Aus dem Äquivalent von setInterval in 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

Verwendung:

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

Oder hier ist die gleiche Funktionalität, aber als eigenständige Funktion anstelle eines Dekorateurs :

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

Hier erfahren Sie, wie Sie dies ohne Verwendung von Threads tun .

jfs
quelle
Wie würden Sie das Intervall ändern, wenn Sie einen Dekorateur verwenden? Angenommen, ich möchte .5s zur Laufzeit auf 1 Sekunde ändern oder was auch immer?
lightxx
@lightxx: einfach benutzen @setInterval(1).
JFS
Hm. Entweder bin ich ein bisschen langsam oder du hast mich missverstanden. Ich meinte zur Laufzeit. Ich weiß, dass ich den Dekorator im Quellcode jederzeit ändern kann. Ich hatte zum Beispiel drei Funktionen, die jeweils mit einem @setInterval (n) dekoriert waren. jetzt zur Laufzeit möchte ich das Intervall von Funktion 2 ändern, aber die Funktionen 1 und 3 in Ruhe lassen.
lightxx
@lightxx: Sie könnten verschiedene Schnittstellen verwenden, z stop = repeat(every=second, call=your_function); ...; stop().
JFS
31

Verwenden von Timer-Threads

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

Dies kann durch gestoppt werden t.cancel()

Swapnil Jariwala
quelle
3
Ich glaube, dieser Code hat einen Fehler in der cancelMethode. Wenn dies aufgerufen wird, läuft der Thread entweder 1) nicht oder 2). In 1) warten wir darauf, die Funktion auszuführen, so dass Abbrechen gut funktioniert. in 2) laufen wir gerade, so dass Abbrechen keine Auswirkung auf die aktuelle Ausführung hat. Darüber hinaus plant sich die aktuelle Ausführung neu, sodass sie in Zukunft keine Auswirkungen mehr hat.
Rich Episcopo
1
Dieser Code erstellt jedes Mal einen neuen Thread, wenn der Timer abläuft. Dies ist eine kolossale Verschwendung im Vergleich zur akzeptierten Antwort.
Adrian W
Diese Lösung sollte aus dem oben genannten Grund vermieden werden: Sie erstellt jedes Mal einen neuen Thread
Pynchia
17

Wenn wir die Antwort von Hans Then ein wenig verbessern , können wir einfach die Timer-Funktion unterordnen. Das Folgende wird zu unserem gesamten "Wiederholungs-Timer" -Code und kann als Drop-In-Ersatz für Threading verwendet werden. Timer mit denselben Argumenten:

from threading import Timer

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

Anwendungsbeispiel:

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

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

erzeugt die folgende Ausgabe:

foo
foo
foo
foo

und

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

produziert

bar
bar
bar
bar
right2clicky
quelle
Kann ich mit diesem Ansatz den Timer-Thread starten / abbrechen / starten / abbrechen?
Paul Knopf
1
Nein. Während dieser Ansatz es Ihnen ermöglicht, alles zu tun, was Sie mit einem normalen Timer tun würden, können Sie dies nicht mit einem normalen Timer tun. Da start / cancel mit dem zugrunde liegenden Thread zusammenhängt, erhalten Sie eine Ausnahme, wenn Sie versuchen, einen Thread zu starten, der zuvor mit .cancel () bearbeitet wurde RuntimeError: threads can only be started once.
right2clicky
Wirklich elegante Lösung! Seltsam, dass sie nicht nur eine Klasse aufgenommen haben, die dies tut.
Roger Dahl
Diese Lösung ist sehr beeindruckend, aber ich hatte Schwierigkeiten zu verstehen, wie sie entwickelt wurde, indem ich einfach die Dokumentation zur Python3-Threading-Timer-Oberfläche las . Die Antwort scheint darauf aufzubauen, die Implementierung zu kennen, indem Sie in das threading.pyModul selbst gehen.
Adam.at.Epsilon
13

Im Interesse einer korrekten Antwort mit Timer, wie vom OP angefordert, werde ich die Antwort von Swapnil Jariwala verbessern :

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()
Bill Schumacher
quelle
2

Ich musste das für ein Projekt tun. Am Ende habe ich einen separaten Thread für die Funktion gestartet

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

**** Herzschlag ist meine Funktion, Arbeiter ist eines meiner Argumente ****

innerhalb meiner Herzschlagfunktion:

def heartbeat(worker):

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

Wenn ich den Thread starte, wartet die Funktion wiederholt 5 Sekunden, führt meinen gesamten Code aus und macht das auf unbestimmte Zeit. Wenn Sie den Prozess beenden möchten, beenden Sie einfach den Thread.

Andrew Wilkins
quelle
2

Ich habe einen Code im Swapnil-Jariwala-Code geändert, um eine kleine Konsolenuhr zu erstellen.

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

AUSGABE

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

Timer mit tkinter Grafikschnittstelle

Dieser Code versetzt den Timer mit tkinter in ein kleines Fenster

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

Ein Beispiel für ein Kartenspiel (eine Art)

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

Ausgabe:

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?
...
Giovanni G. PY
quelle
Wenn hFunction blockiert, würde dies nicht zu einer Verzögerung der nachfolgenden Startzeiten führen? Vielleicht könnten Sie die Zeilen vertauschen, damit handle_function zuerst den Timer startet und dann hFunction aufruft?
Schnurrbart
1

Ich habe eine Klasse implementiert, die als Timer funktioniert.

Ich lasse den Link hier, falls jemand ihn benötigt: https://github.com/ivanhalencp/python/tree/master/xTimer

Ivan Coppes
quelle
3
Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
Karl Richter
1
from threading import Timer
def TaskManager():
    #do stuff
    t = Timer( 1, TaskManager )
    t.start()

TaskManager()

Hier ist ein kleines Beispiel, das Ihnen hilft, besser zu verstehen, wie es läuft. Die Funktion taskManager () erstellt am Ende einen verzögerten Funktionsaufruf.

Versuchen Sie, die Variable "dalay" zu ändern, und Sie werden einen Unterschied feststellen können

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
ch3ll0v3k
quelle
1
Können Sie auch etwas mehr darüber erzählen, warum dies funktioniert?
Minocha
Ihr Beispiel war etwas verwirrend, der erste Codeblock war alles, was Sie sagen mussten.
Partack
0

Ich mag die Antwort von right2clicky, insbesondere, weil nicht jedes Mal, wenn der Timer tickt, ein Thread abgerissen und ein neuer erstellt werden muss. Darüber hinaus ist es einfach, eine Klasse mit einem Timer-Rückruf zu erstellen, der regelmäßig aufgerufen wird. Das ist mein normaler Anwendungsfall:

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()
Frank P.
quelle
0

Dies ist eine alternative Implementierung, die Funktion anstelle von Klasse verwendet. Inspiriert von @Andrew Wilkins oben.

Weil das Warten genauer ist als das Schlafen (es berücksichtigt die Funktionslaufzeit):

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()
Paul Kenjora
quelle
0

Ich habe eine andere Lösung mit der SingleTon-Klasse gefunden. Bitte sagen Sie mir, ob hier ein Speicherverlust vorliegt.

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