167 votes

Exécution d'actions périodiques

Je travaille sous Windows. Je veux exécuter une fonction foo() toutes les 10 secondes.
Comment dois-je m'y prendre ?

0 votes

Il semble que la bibliothèque standard elle-même ne fournisse aucune fonction pour cela. Vous pouvez regarder les implémentations de boucles d'événements.

0 votes

comptage chronométré est un bon remplacement pour une boucle qui contient un appel à time.sleep . Elle est précise, ne dépend pas du temps d'exécution de la boucle et n'accumule pas de dérive temporelle.

218voto

kev Points 41855

A la fin de l'année foo() créer un Timer qui appelle foo() après 10 secondes.
Parce que, Timer créer un nouveau thread d'appeler foo() .
Vous pouvez faire d'autres choses sans être bloqué.

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

58 votes

Une chose à laquelle il faut faire attention ici est la "dérive" du temps de départ. Je viens de faire un test et mes temps ont dérivé de +0.05s en environ 33 itérations. Je faisais des sondages d'une seconde, ce qui signifie une dérive de 20% en moins d'une minute. Vous pouvez réduire le site dérive en appelant la threading.Timer au début de la fonction plutôt qu'à la fin, mais seulement si c'est la durée de la fonction qui est à l'origine de votre dérive, et non le manque de fiabilité du chronomètre. La meilleure façon de réduire la dérive est de ne dormir qu'aussi longtemps que nécessaire jusqu'au prochain temps d'exécution prévu. Je vais ajouter un exemple comme autre réponse.

3 votes

Je n'ai pas trouvé de bonne solution à ce problème mais j'y ai réfléchi un peu et je vais bientôt poster une réponse ci-dessous qui utilise un générateur.

8 votes

Qu'en est-il de l'utilisation de la mémoire ici ? Cela ressemble à un appel récursif infini, n'est-ce pas ?

127voto

Michael Anderson Points 21181

Il suffit de dormir pendant 10 secondes ou d'utiliser threading.Timer(10,foo) entraînera une dérive de l'heure de démarrage. (Il se peut que vous ne vous en souciiez pas, ou que ce soit une source importante de problèmes, selon votre situation exacte). Il peut y avoir deux causes à cela - des inexactitudes dans le temps de réveil de votre thread ou le temps d'exécution de votre fonction.

Vous pouvez voir quelques résultats à la fin de ce post, mais d'abord un exemple de la façon de le corriger. Vous devez déterminer à quel moment votre fonction devrait être appelée et à quel moment elle a été appelée et tenir compte de la différence.

Voici une version qui dérive légèrement :

import datetime, threading

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

foo()

Sa sortie ressemble à ceci :

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

Vous pouvez voir que le nombre de sous-secondes augmente constamment et que, par conséquent, l'heure de départ "dérive".

Il s'agit d'un code qui tient compte correctement de la dérive :

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

Sa sortie ressemble à ceci :

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

Ici, vous pouvez voir qu'il n'y a plus d'augmentation des temps inférieurs à la seconde.

Si vos événements sont très fréquents, vous voudrez peut-être exécuter la minuterie dans un seul thread, plutôt que de démarrer un nouveau thread pour chaque événement. En tenant compte de la dérive, cela pourrait ressembler à ceci :

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

Cependant, votre application ne se terminera pas normalement, vous devrez tuer le fil de la minuterie. Si vous souhaitez quitter normalement votre application lorsque celle-ci est terminée, sans tuer manuellement le fil d'exécution, vous devez utiliser la commande

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

7 votes

Il semble inutile de créer un thread pour chaque appel. Vous pourriez le faire dans un seul fil

0 votes

@J.F.Sebastian d'accord, ceci a été principalement implémenté comme une extension de la réponse la plus votée. L'overhead du thread est généralement assez faible, mais si vos actions sont fréquentes, vous devez faire quelque chose de différent - exécuter l'action comme un seul thread est une extension triviale (mais souvent importante), certains systèmes utilisent également des structures de données dédiées afin que de nombreux événements puissent être planifiés sur un seul thread (ce qui n'est pas si trivial).

14voto

Raymond Hettinger Points 50330

Peut-être que le module de programmation répondra à vos besoins.

Vous pouvez également envisager d'utiliser un Objet temporisé .

1 votes

Le module d'ordonnancement est le moyen le plus souple d'y parvenir. Merci pour le lien.

0 votes

Pourriez-vous fournir un exemple de code ? Je veux croire que la bibliothèque standard Python résout ce problème, mais la documentation de sched ne montre pas comment. Comment implémenter une minuterie précise avec sched ?

11voto

wim Points 35274

Ceci insérera un sommeil de 10 secondes entre chaque appel à foo() ce qui correspond à peu près à ce que vous avez demandé si l'appel se termine rapidement.

import time

while True:
    foo()
    time.sleep(10)

Pour faire d'autres choses pendant que votre foo() est appelé dans un thread d'arrière-plan

import time
import sys
import threading

def foo():
    sys.stdout.write('({}) foo\n'.format(time.ctime()))

def foo_target():
    while True:
        foo()
        time.sleep(10)

t = threading.Thread(target=foo_target)
t.daemon = True
t.start()
print('doing other things...')

0 votes

Je veux aussi faire d'autres choses en attendant. Existe-t-il un moyen d'utiliser les signaux ?

0 votes

Si votre foo() prend un temps inconnu pour se terminer, vous voudriez créer un thread pour exécuter un foo() toutes les 10 secondes, je peux vous montrer comment faire si nécessaire.

0 votes

Est-ce que foo est un appel rapide, ou est-ce que cela prend quelques secondes ?

7voto

Richard Logwood Points 498

Voici une belle mise en œuvre utilisant la classe Thread : http://g-off.net/software/a-python-repeatable-threadingtimer-class

le code ci-dessous est un peu plus rapide et sale :

from threading import Timer
from time import sleep

def hello():
    print "hello, world"
    t = Timer(3,hello)
    t.start()

t = Timer(3, hello)
t.start() # after 3 seconds, "hello, world" will be printed

# timer will wake up ever 3 seconds, while we do something else
while True:
    print "do something else"
    sleep(10)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X