Python threading.timer - fonction de répétition toutes les 'n' secondes

94

Je veux déclencher une fonction toutes les 0,5 seconde et pouvoir démarrer, arrêter et réinitialiser la minuterie. Je ne connais pas trop bien le fonctionnement des threads Python et j'ai des difficultés avec le minuteur python.

Cependant, je reçois RuntimeError: threads can only be started oncequand j'exécute threading.timer.start()deux fois. Y a-t-il une solution pour cela? J'ai essayé de postuler threading.timer.cancel()avant chaque départ.

Pseudo code:

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

Réponses:

112

La meilleure façon est de démarrer le fil de la minuterie une fois. Dans votre fil de minuterie, vous coderiez ce qui suit

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

Dans le code qui a démarré la minuterie, vous pouvez alors setl'événement arrêté pour arrêter la minuterie.

stopFlag = Event()
thread = MyThread(stopFlag)
thread.start()
# this will stop the timer
stopFlag.set()
Hans alors
la source
4
Ensuite, il terminera son sommeil et s'arrêtera ensuite. Il n'y a aucun moyen de suspendre de force un thread en python. Il s'agit d'une décision de conception prise par les développeurs python. Cependant, le résultat net sera le même. Votre thread continuera à fonctionner (en veille) pendant un court moment, mais il n'exécutera pas votre fonction.
Hans puis
13
Eh bien, en fait, si vous voulez pouvoir arrêter immédiatement le thread du minuteur, utilisez simplement threading.Eventet waitau lieu de sleep. Ensuite, pour le réveiller, réglez simplement l'événement. Vous n'avez même pas besoin du self.stoppedalors parce que vous vérifiez simplement le drapeau d'événement.
nneonneo
3
L'événement serait utilisé strictement pour interrompre le thread du minuteur. Normalement, le event.waitdélai expirait et agirait comme un sommeil, mais si vous vouliez arrêter (ou interrompre autrement le thread), vous définiriez l'événement du thread et il se réveillerait immédiatement.
nneonneo
2
J'ai mis à jour ma réponse pour utiliser event.wait (). Merci pour les suggestions.
Hans puis
1
juste une question, comment puis-je redémarrer le fil après cela? appeler thread.start()me donnethreads can only be started once
Motassem MK
33

De l' équivalent 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

Usage:

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

Ou voici la même fonctionnalité mais en tant que fonction autonome au lieu d'un décorateur :

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

Voici comment le faire sans utiliser de threads .

jfs
la source
comment changeriez-vous l'intervalle lors de l'utilisation d'un décorateur? dire que je veux changer .5s au moment de l'exécution en 1 seconde ou autre?
lightxx
@lightxx: il suffit d'utiliser @setInterval(1).
jfs
hm. alors soit je suis un peu lent, soit tu m'as mal compris. Je voulais dire au moment de l'exécution. Je sais que je peux changer le décorateur dans le code source à tout moment. quoi, par exemple, j'avais trois fonctions, chacune décorée d'un @setInterval (n). maintenant, au moment de l'exécution, je veux changer l'intervalle de la fonction 2 mais laisser les fonctions 1 et 3 seules.
lightxx
@lightxx: vous pouvez utiliser une interface différente, par exemple stop = repeat(every=second, call=your_function); ...; stop().
jfs
31

Utilisation des threads de minuterie

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

cela peut être arrêté par t.cancel()

swapnil jariwala
la source
3
Je crois que ce code a un bogue dans la cancelméthode. Lorsqu'il est appelé, le thread est soit 1) non en cours d'exécution, soit 2) en cours d'exécution. Dans 1), nous attendons d'exécuter la fonction, donc annuler fonctionnera correctement. dans 2) nous sommes en cours d'exécution, donc annuler n'aura aucun effet sur l'exécution en cours. de plus, l'exécution en cours se replanifie d'elle-même pour ne pas avoir d'effet dans le futur.
Rich Episcopo
1
Ce code crée un nouveau thread chaque fois que le minuteur s'arrête. C'est un gaspillage colossal par rapport à la réponse acceptée.
Adrian W
Cette solution est à éviter, pour la raison évoquée ci-dessus: elle crée un nouveau thread à chaque fois
Pynchia
17

En améliorant un peu la réponse de Hans Then , nous pouvons simplement sous-classer la fonction Timer. Ce qui suit devient l' intégralité de notre code de "minuterie de répétition", et il peut être utilisé comme remplacement de threading.Timer avec tous les mêmes arguments:

from threading import Timer

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

Exemple d'utilisation:

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

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

produit la sortie suivante:

foo
foo
foo
foo

et

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

produit

bar
bar
bar
bar
right2clicky
la source
Cette approche me permettra-t-elle de démarrer / annuler / démarrer / annuler le fil de la minuterie?
Paul Knopf
1
Non. Bien que cette approche vous permette de faire tout ce que vous feriez avec une minuterie ordinaire, vous ne pouvez pas le faire avec une minuterie ordinaire. Depuis début / cancel est lié au fil sous - jacente, si vous essayez de .START () un fil qui a déjà été .cancel () 'ed alors vous obtiendrez une exception, RuntimeError: threads can only be started once.
right2clicky
Solution vraiment élégante! Bizarre qu'ils n'incluent pas seulement une classe qui fait cela.
Roger Dahl
cette solution est très impressionnante, mais j'ai eu du mal à comprendre comment elle a été conçue en lisant simplement la documentation de l'interface Python3 threading Timer . La réponse semble s'appuyer sur la connaissance de l'implémentation en allant dans le threading.pymodule lui-même.
Adam.at.Epsilon
14

Dans l'intérêt de fournir une réponse correcte en utilisant Timer comme l'OP l'a demandé, je vais améliorer la réponse 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
la source
3

J'ai changé du code dans le code swapnil-jariwala pour créer une petite horloge de 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()

PRODUCTION

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

Minuterie avec une interface graphique tkinter

Ce code met l'horloge dans une petite fenêtre avec 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 exemple de jeu de cartes mémoire (en quelque sorte)

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

production:

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
la source
si hFunction se bloque, cela n'ajouterait-il pas un certain retard aux heures de démarrage suivantes? Peut-être pourriez-vous inverser les lignes pour que handle_function démarre d'abord le minuteur, puis appelle hFunction?
Moustache
2

Je devais faire ça pour un projet. Ce que j'ai fini par faire était de démarrer un thread séparé pour la fonction

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

**** Le battement de coeur est ma fonction, le travailleur est l'un de mes arguments ****

à l'intérieur de ma fonction de rythme cardiaque:

def heartbeat(worker):

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

Ainsi, lorsque je démarre le thread, la fonction attendra à plusieurs reprises 5 secondes, exécutera tout mon code et le fera indéfiniment. Si vous voulez tuer le processus, il suffit de tuer le thread.

Andrew Wilkins
la source
1

J'ai implémenté une classe qui fonctionne comme une minuterie.

Je laisse le lien ici au cas où quelqu'un en aurait besoin: https://github.com/ivanhalencp/python/tree/master/xTimer

Ivan Coppes
la source
3
Bien que cela puisse théoriquement répondre à la question, il serait préférable d'inclure ici les parties essentielles de la réponse et de fournir le lien pour référence.
Karl Richter
1
from threading import Timer
def TaskManager():
    #do stuff
    t = Timer( 1, TaskManager )
    t.start()

TaskManager()

Voici un petit échantillon, cela vous aidera à mieux comprendre comment il fonctionne. function taskManager () à la fin crée un appel de fonction retardé à lui-même.

Essayez de changer la variable "dalay" et vous pourrez voir la différence

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
la source
1
pouvez-vous également en dire un peu plus sur les raisons pour lesquelles cela fonctionne?
minocha
votre exemple était un peu déroutant, le premier bloc de code était tout ce que vous aviez à dire.
Partack du
1

J'aime la réponse de right2clicky, en particulier en ce qu'elle ne nécessite pas la suppression d'un Thread et la création d'un nouveau à chaque fois que le Timer tourne. De plus, il est facile de créer une classe avec un rappel de minuterie qui est appelé périodiquement. C'est mon cas d'utilisation 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
la source
1

Il s'agit d'une implémentation alternative utilisant la fonction au lieu de la classe. Inspiré par @Andrew Wilkins ci-dessus.

Parce que wait est plus précis que sleep (il prend en compte l'exécution de la fonction):

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
la source
1

J'ai mis au point une autre solution avec la classe SingleTon. S'il vous plaît dites-moi si une fuite de mémoire est là.

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
la source