Ejecutar acciones periódicas [duplicado]

Resuelto Bruce asked hace 12 años • 8 respuestas

Estoy trabajando en Windows. Quiero ejecutar una función foo()cada 10 segundos.
¿Cómo hago esto?

Bruce avatar Dec 22 '11 13:12 Bruce
Aceptado

Al final de foo(), cree un Timerque se llame foo()a sí mismo después de 10 segundos.
Porque, Timercrea un nuevo threadpara llamar foo().
Puedes hacer otras cosas sin que te bloqueen.

import time, threading
def foo():
    print(time.ctime())
    threading.Timer(10, foo).start()

foo()

#output:
#Thu Dec 22 14:46:08 2011
#Thu Dec 22 14:46:18 2011
#Thu Dec 22 14:46:28 2011
#Thu Dec 22 14:46:38 2011
kev avatar Dec 22 '2011 06:12 kev

Simplemente dormir durante 10 segundos o usarlo threading.Timer(10,foo)provocará una desviación del tiempo de inicio. (Es posible que esto no le importe, o puede ser una fuente importante de problemas dependiendo de su situación exacta). Puede haber dos causas para esto: imprecisiones en el tiempo de activación de su subproceso o el tiempo de ejecución de su función.

Puedes ver algunos resultados al final de esta publicación, pero primero un ejemplo de cómo solucionarlo. Debe realizar un seguimiento de cuándo se debe llamar a continuación a su función en lugar de cuándo se llamó realmente y tener en cuenta la diferencia.

Aquí hay una versión que se desvía ligeramente:

import datetime, threading

def foo():
    print datetime.datetime.now()
    threading.Timer(1, foo).start()

foo()

Su salida se ve así:

2013-08-12 13:05:36.483580
2013-08-12 13:05:37.484931
2013-08-12 13:05:38.485505
2013-08-12 13:05:39.486945
2013-08-12 13:05:40.488386
2013-08-12 13:05:41.489819
2013-08-12 13:05:42.491202
2013-08-12 13:05:43.492486
2013-08-12 13:05:44.493865
2013-08-12 13:05:45.494987
2013-08-12 13:05:46.496479
2013-08-12 13:05:47.497824
2013-08-12 13:05:48.499286
2013-08-12 13:05:49.500232

Puede ver que el conteo de subsegundos aumenta constantemente y, por lo tanto, el tiempo de inicio está "a la deriva".

Este es el código que explica correctamente la deriva:

import datetime, threading, time

next_call = time.time()

def foo():
  global next_call
  print datetime.datetime.now()
  next_call = next_call+1
  threading.Timer( next_call - time.time(), foo ).start()

foo()

Su salida se ve así:

2013-08-12 13:21:45.292565
2013-08-12 13:21:47.293000
2013-08-12 13:21:48.293939
2013-08-12 13:21:49.293327
2013-08-12 13:21:50.293883
2013-08-12 13:21:51.293070
2013-08-12 13:21:52.293393

Aquí puede ver que ya no hay ningún aumento en los tiempos inferiores al segundo.

Si sus eventos ocurren con mucha frecuencia, es posible que desee ejecutar el temporizador en un solo hilo, en lugar de iniciar un nuevo hilo para cada evento. Teniendo en cuenta la deriva, esto se vería así:

import datetime, threading, time

def foo():
    next_call = time.time()
    while True:
        print datetime.datetime.now()
        next_call = next_call+1;
        time.sleep(next_call - time.time())

timerThread = threading.Thread(target=foo)
timerThread.start()

Sin embargo, su aplicación no saldrá normalmente, deberá finalizar el hilo del temporizador. Si desea salir normalmente cuando su aplicación haya terminado, sin cerrar manualmente el hilo, debe usar

timerThread = threading.Thread(target=foo)
timerThread.daemon = True
timerThread.start()
Michael Anderson avatar Aug 12 '2013 05:08 Michael Anderson

Me sorprende no encontrar una solución utilizando un generador para cronometrar. Acabo de diseñar este para mis propios fines.

Esta solución: subproceso único, sin creación de instancias de objetos en cada período, utiliza un generador de tiempos, sólido como una roca en el tiempo hasta la precisión del timemódulo (a diferencia de varias de las soluciones que he probado desde el intercambio de pila).

Nota: para Python 2.x, reemplace next(g)a continuación con g.next().

import time

def do_every(period,f,*args):
    def g_tick():
        t = time.time()
        while True:
            t += period
            yield max(t - time.time(),0)
    g = g_tick()
    while True:
        time.sleep(next(g))
        f(*args)

def hello(s):
    print('hello {} ({:.4f})'.format(s,time.time()))
    time.sleep(.3)

do_every(1,hello,'foo')

Resultados en, por ejemplo:

hello foo (1421705487.5811)
hello foo (1421705488.5811)
hello foo (1421705489.5809)
hello foo (1421705490.5830)
hello foo (1421705491.5803)
hello foo (1421705492.5808)
hello foo (1421705493.5811)
hello foo (1421705494.5811)
hello foo (1421705495.5810)
hello foo (1421705496.5811)
hello foo (1421705497.5810)
hello foo (1421705498.5810)
hello foo (1421705499.5809)
hello foo (1421705500.5811)
hello foo (1421705501.5811)
hello foo (1421705502.5811)
hello foo (1421705503.5810)

Tenga en cuenta que este ejemplo incluye una simulación de la CPU haciendo otra cosa durante 0,3 segundos cada período. Si lo cambiaras para que fuera aleatorio cada vez, no importaría. El máximo en la yieldlínea sirve para proteger sleepcontra números negativos en caso de que la función que se llama demore más que el período especificado. En ese caso, se ejecutaría inmediatamente y recuperaría el tiempo perdido en el momento de la siguiente ejecución.

watsonic avatar Jan 19 '2015 22:01 watsonic

Quizás el módulo programado satisfaga sus necesidades.

Alternativamente, considere usar un objeto Timer .

Raymond Hettinger avatar Dec 22 '2011 08:12 Raymond Hettinger