Python threading.timer - ripeti la funzione ogni 'n' secondi


94

Voglio attivare una funzione ogni 0,5 secondi ed essere in grado di avviare e arrestare e azzerare il timer. Non sono troppo informato su come funzionano i thread Python e sto avendo difficoltà con il timer Python.

Tuttavia, continuo a ricevere RuntimeError: threads can only be started oncequando eseguo threading.timer.start()due volte. C'è una soluzione per questo? Ho provato ad applicare threading.timer.cancel()prima di ogni inizio.

Pseudo codice:

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

Risposte:


112

Il modo migliore è avviare una volta il thread del timer. All'interno del thread del timer dovresti codificare quanto segue

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

Nel codice che ha avviato il timer, è quindi possibile setinterrompere l'evento interrotto per arrestare il timer.

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

4
Quindi finirà di dormire e si fermerà dopo. Non c'è modo di sospendere forzatamente un thread in Python. Questa è una decisione di progettazione presa dagli sviluppatori python. Tuttavia il risultato netto sarà lo stesso. Il thread continuerà a funzionare (dormire) per un breve periodo, ma non eseguirà la tua funzione.
Hans Then

13
Bene, in realtà, se vuoi essere in grado di fermare immediatamente il thread del timer, usa threading.Evente waitinvece di sleep. Quindi, per riattivarlo, basta impostare l'evento. Non hai nemmeno bisogno self.stoppeddell'allarme perché controlli semplicemente il flag dell'evento.
nneonneo

3
L'evento verrebbe utilizzato rigorosamente per interrompere il thread del timer. Normalmente, il event.waitthread andrebbe semplicemente in timeout e si comporterebbe come uno sleep, ma se volessi fermare (o altrimenti interrompere il thread) dovresti impostare l'evento del thread e si riattiverebbe immediatamente.
nneonneo

2
Ho aggiornato la mia risposta per utilizzare event.wait (). Grazie per i suggerimenti.
Hans Then

1
solo una domanda, come posso riavviare il thread dopo? la chiamata thread.start()mi dàthreads can only be started once
Motassem MK

33

Da equivalente di 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

Utilizzo:

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

Oppure ecco la stessa funzionalità ma come funzione autonoma invece di un decoratore :

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

Ecco come farlo senza usare i thread .


come cambiereste l'intervallo quando usate un decoratore? dico che voglio cambiare .5s in fase di esecuzione a 1 secondo o altro?
lightxx

@lightxx: basta usare @setInterval(1).
jfs

hm. quindi o sono un po 'lento o mi hai frainteso. Intendevo in fase di esecuzione. So di poter cambiare il decoratore nel codice sorgente in qualsiasi momento. cosa, ad esempio, avevo tre funzioni, ciascuna decorata con un @setInterval (n). ora in fase di esecuzione voglio cambiare l'intervallo della funzione 2 ma lasciare le funzioni 1 e 3 da sole.
lightxx

@lightxx: potresti usare un'interfaccia diversa, ad esempio stop = repeat(every=second, call=your_function); ...; stop().
jfs


31

Utilizzo dei thread del timer

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

questo può essere fermato t.cancel()


3
Credo che questo codice abbia un bug nel cancelmetodo. Quando viene chiamato, il thread è 1) non in esecuzione o 2) in esecuzione. In 1) stiamo aspettando di eseguire la funzione, quindi annulla funzionerà bene. in 2) siamo attualmente in esecuzione, quindi l'annullamento non avrà alcun effetto sull'esecuzione corrente. inoltre, l'attuale esecuzione viene riprogrammata, quindi non avrà alcun effetto in futuro.
Rich Episcopo

1
Questo codice crea un nuovo thread ogni volta che il timer si esaurisce. Questo è uno spreco colossale rispetto alla risposta accettata.
Adrian W

Questa soluzione dovrebbe essere evitata, per il motivo sopra menzionato: crea ogni volta un nuovo thread
Pynchia

17

Migliorando un po ' la risposta di Hans Then , possiamo semplicemente sottoclassare la funzione Timer. Il codice seguente diventa il nostro intero codice "timer di ripetizione" e può essere utilizzato come sostituto immediato del threading.Timer con tutti gli stessi argomenti:

from threading import Timer

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

Esempio di utilizzo:

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

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

produce il seguente output:

foo
foo
foo
foo

e

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

produce

bar
bar
bar
bar

Questo approccio mi consentirà di avviare / annullare / avviare / annullare il thread del timer?
Paul Knopf

1
No. Sebbene questo approccio ti permetta di fare tutto ciò che faresti con un normale timer, non puoi farlo con un normale timer. Dal momento di avvio / Cancel è legato al filo conduttore, se si tenta di .Start () un filo che è stato in precedenza .CANCEL () 'Ed allora si otterrà un'eccezione, RuntimeError: threads can only be started once.
right2clicky

Soluzione davvero elegante! Strano che non includessero solo una classe che fa questo.
Roger Dahl

questa soluzione è davvero impressionante, ma ho faticato a capire come è stata progettata semplicemente leggendo la documentazione dell'interfaccia del timer di threading di Python3 . La risposta sembra basarsi sulla conoscenza dell'implementazione entrando nel threading.pymodulo stesso.
Adam.at.Epsilon

14

Nell'interesse di fornire una risposta corretta utilizzando Timer come richiesto dall'OP, migliorerò la risposta di 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

Ho cambiato del codice nel codice swapnil-jariwala per creare un piccolo orologio da console.

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

PRODUZIONE

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

Timer con interfaccia grafica tkinter

Questo codice mette il timer dell'orologio in una piccola finestra con 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()

Un esempio di gioco con flashcard (più o meno)

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

produzione:

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?
...

se hFunction sta bloccando, non aggiungerebbe un certo ritardo agli orari di avvio successivi? Forse potresti scambiare le linee in modo che handle_function avvii prima il timer e poi chiami hFunction?
Baffi

2

Ho dovuto farlo per un progetto. Quello che ho finito per fare è stato avviare un thread separato per la funzione

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

**** Il battito cardiaco è la mia funzione, il lavoratore è uno dei miei argomenti ****

all'interno della mia funzione battito cardiaco:

def heartbeat(worker):

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

Quindi, quando avvio il thread, la funzione attenderà ripetutamente 5 secondi, eseguirà tutto il mio codice e lo farà a tempo indeterminato. Se vuoi terminare il processo, elimina il thread.



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

TaskManager()

Ecco un piccolo esempio, aiuterà a capire meglio come funziona. la funzione taskManager () alla fine crea una chiamata ritardata a se stessa.

Prova a cambiare la variabile "dalay" e vedrai la differenza

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
puoi anche dire qualcosa di più sul motivo per cui funziona?
minocha

il tuo esempio è stato un po 'confuso, il primo blocco di codice era tutto ciò che dovevi dire.
Partack

1

Mi piace la risposta di right2clicky, soprattutto in quanto non richiede che un thread venga abbattuto e ne venga creato uno nuovo ogni volta che il timer scatta. Inoltre, è un facile override creare una classe con una richiamata del timer che viene chiamata periodicamente. Questo è il mio normale caso d'uso:

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

Questa è un'implementazione alternativa che utilizza la funzione anziché la classe. Ispirato da @Andrew Wilkins sopra.

Perché l'attesa è più accurata della sospensione (prende in considerazione il runtime della funzione):

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

Ho trovato un'altra soluzione con la classe SingleTon. Per favore dimmi se c'è qualche perdita di memoria qui.

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.