Python threading.timer - repetir la función cada 'n' segundos

94

Quiero activar una función cada 0,5 segundos y poder iniciar, detener y restablecer el temporizador. No conozco demasiado cómo funcionan los hilos de Python y tengo dificultades con el temporizador de Python.

Sin embargo, sigo obteniendo RuntimeError: threads can only be started oncecuando ejecuto threading.timer.start()dos veces. ¿Hay una solución para esto? Intenté aplicar threading.timer.cancel()antes de cada inicio.

Pseudo código:

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

Respuestas:

112

La mejor manera es iniciar el hilo del temporizador una vez. Dentro de su hilo de temporizador, codificaría lo siguiente

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

En el código que inició el temporizador, puede setdetener el evento detenido para detener el temporizador.

stopFlag = Event()
thread = MyThread(stopFlag)
thread.start()
# this will stop the timer
stopFlag.set()
Hans entonces
fuente
4
Luego terminará su sueño y se detendrá después. No hay forma de suspender por la fuerza un hilo en Python. Esta es una decisión de diseño tomada por los desarrolladores de Python. Sin embargo, el resultado neto será el mismo. Su subproceso seguirá ejecutándose (inactivo) durante un tiempo, pero no realizará su función.
Hans Entonces
13
Bueno, en realidad, si usted quiere ser capaz de detener el hilo temporizador de inmediato, sólo tiene que utilizar una threading.Eventy waiten lugar de sleep. Luego, para despertarlo, simplemente configure el evento. Ni siquiera necesita el self.stoppedentonces porque solo verifica la bandera del evento.
nneonneo
3
El evento se usaría estrictamente para interrumpir el hilo del temporizador. Normalmente, el event.waitsolo se agotaría y actuaría como un sueño, pero si quisiera detener (o interrumpir el hilo), establecería el evento del hilo y se despertaría inmediatamente.
nneonneo
2
He actualizado mi respuesta para usar event.wait (). Gracias por las sugerencias.
Hans Entonces
1
solo una pregunta, ¿cómo puedo reiniciar el hilo después de eso? la llamada thread.start()me dathreads can only be started once
Motassem MK
33

De Equivalente de setInterval en 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

Uso:

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

O esta es la misma funcionalidad pero como una función independiente en lugar de un decorador :

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

A continuación, le indicamos cómo hacerlo sin utilizar hilos .

jfs
fuente
¿Cómo cambiarías el intervalo al usar un decorador? digo que quiero cambiar .5s en tiempo de ejecución a 1 segundo o lo que sea?
lightxx
@lightxx: solo usa @setInterval(1).
jfs
hm. así que o soy un poco lento o me malinterpretaste. Me refiero al tiempo de ejecución. Sé que puedo cambiar el decorador en el código fuente en cualquier momento. lo que, por ejemplo, tenía tres funciones, cada una decorada con un @setInterval (n). ahora, en tiempo de ejecución, quiero cambiar el intervalo de la función 2, pero dejo las funciones 1 y 3 solas.
lightxx
@lightxx: se puede usar interfaz diferente, por ejemplo, stop = repeat(every=second, call=your_function); ...; stop().
jfs
31

Usando hilos de temporizador

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

esto puede ser detenido por t.cancel()

swapnil jariwala
fuente
3
Creo que este código tiene un error en el cancelmétodo. Cuando se llama a esto, el hilo 1) no se está ejecutando o 2) se está ejecutando. En 1) estamos esperando ejecutar la función, por lo que cancelar funcionará bien. en 2) estamos ejecutando actualmente, por lo que cancelar no tendrá ningún efecto en la ejecución actual. además, la ejecución actual se reprograma a sí misma para que no tenga ningún efecto en el futuro.
Rich Episcopo
1
Este código crea un nuevo hilo cada vez que se agota el temporizador. Este es un desperdicio colosal en comparación con la respuesta aceptada.
Adrian W.
Esta solución debe evitarse, por la razón mencionada anteriormente: crea un nuevo hilo cada vez
Pynchia
17

Mejorando un poco la respuesta de Hans Then , podemos simplemente subclasificar la función Timer. Lo siguiente se convierte en nuestro código completo de "temporizador de repetición", y se puede usar como un reemplazo directo para el subproceso. Temporizador con los mismos argumentos:

from threading import Timer

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

Ejemplo de uso:

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

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

produce la siguiente salida:

foo
foo
foo
foo

y

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

produce

bar
bar
bar
bar
right2clicky
fuente
¿Este enfoque me permitirá iniciar / cancelar / iniciar / cancelar el hilo del temporizador?
Paul Knopf
1
No. Si bien este enfoque le permite hacer cualquier cosa que haría con un temporizador normal, no puede hacerlo con un temporizador normal. Desde inicio / Cancelar se relaciona con el hilo subyacente, si se intenta .start () un hilo que ha sido previamente .cancel () 'Ed por lo que recibirá una excepción, RuntimeError: threads can only be started once.
right2clicky
¡Solución realmente elegante! Es extraño que no solo incluyeran una clase que hace esto.
Roger Dahl
esta solución es muy impresionante, pero me costó entender cómo se diseñó simplemente leyendo la documentación de la interfaz del temporizador de subprocesos de Python3 . La respuesta parece basarse en conocer la implementación al entrar en el threading.pymódulo en sí.
Adam
15

Con el interés de proporcionar una respuesta correcta usando Timer como lo solicitó el OP, mejoraré la respuesta de 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()
Bill Schumacher
fuente
3

He cambiado algo de código en el código swapnil-jariwala para hacer un pequeño reloj de consola.

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

SALIDA

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

Temporizador con interfaz gráfica tkinter

Este código coloca el temporizador del reloj en una pequeña ventana 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 ejemplo de juego de tarjetas didácticas (más o menos)

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

salida:

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
fuente
si hFunction está bloqueando, ¿esto no agregaría algún retraso a los tiempos de inicio posteriores? ¿Quizás podría intercambiar las líneas para que handle_function inicie el temporizador primero y luego llame a hFunction?
Bigote
2

Tuve que hacer esto para un proyecto. Lo que terminé haciendo fue iniciar un hilo separado para la función

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

**** El latido del corazón es mi función, el trabajador es uno de mis argumentos ****

dentro de la función de latido de mi corazón:

def heartbeat(worker):

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

Entonces, cuando inicio el hilo, la función esperará repetidamente 5 segundos, ejecutará todo mi código y lo hará indefinidamente. Si desea matar el proceso, simplemente elimine el hilo.

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

TaskManager()

Aquí hay una pequeña muestra, ayudará a comprender mejor cómo funciona. function taskManager () al final crea una llamada de función retrasada a sí misma.

Intente cambiar la variable "dalay" y podrá ver la diferencia

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
fuente
1
¿También puedes contarnos un poco más por qué funciona esto?
minocha
su ejemplo fue un poco confuso, el primer bloque de código fue todo lo que necesitaba decir.
Participación el
1

Me gusta la respuesta de right2clicky, especialmente porque no requiere que se elimine un hilo y se cree uno nuevo cada vez que el temporizador funciona. Además, es fácil anular la creación de una clase con una devolución de llamada de temporizador que se llama periódicamente. Ese es mi caso de uso normal:

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
fuente
1

Esta es una implementación alternativa que usa función en lugar de clase. Inspirado por @Andrew Wilkins arriba.

Porque esperar es más preciso que dormir (tiene en cuenta el tiempo de ejecución de la función):

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
fuente
1

Se me ocurrió otra solución con la clase SingleTon. Dígame si hay alguna pérdida de memoria aquí.

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
fuente