threading.Timer wiederverwenden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

Hi,

ich habe 2 probleme mit dem threading.Timer.

und zwar muss ich ziemlich oft einen timer starten.
nur kann man einen thread ja nur einmal starten.

wie kann ich einen timer also wiederverwenden.
weil immer wieder neue erstellen ist ja wahrscheinlich auch keine gute lösung? (oder ist das kein problem?)

ausserdem bräuchte ich eine möglichkeit festzustellen ob Timer.cancel den timer wirklich gestoppt hat, oder ob er vorher abgelaufen ist.

danke schonmal :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

und zwar muss ich ziemlich oft einen timer starten.
nur kann man einen thread ja nur einmal starten.

wie kann ich einen timer also wiederverwenden.
Denke nicht, das das mit der Timer-Klasse aus threading möglich ist.
weil immer wieder neue erstellen ist ja wahrscheinlich auch keine gute lösung? (oder ist das kein problem?)
Jepp, so toll ist das nicht, doch oft vernachlässigbar. Besser ist es, einen einmal aufgemachten Thread auch zu nutzen. Die Threaderzeugung kostet halt und die max. Anzahl ist auch limitiert.

Du kommst hier wahrscheinlich mit einem selbstgestrickten Timer besser zu Rande. Ich weiß zwar nicht, wie Du die Timer nutzen willst, doch hier mal ein Grobschema für einen wiederverwendbaren Timer:
- Threadklasse bauen
- mit zwei Events versehen, einer zum Schlafen legen der andere für die Timerfunktionalität via Event.wait(timeout)
- mit jeder neuen Timeraufgabe wird Event 1 True gesetzt und der Thread wartet dann auf Event 2 mit timeout, Aufgabe ausführen, danach Event 1 wieder False setzen usw.

Brauchst Du mehrere Timer gleichzeitig, müßtest Du hiervon entsprechend viele Thread-Instanzen initialisieren.
ausserdem bräuchte ich eine möglichkeit festzustellen ob Timer.cancel den timer wirklich gestoppt hat, oder ob er vorher abgelaufen ist.
Ehm, das merkst Du doch, ob die Aktion ausgeführt wurde. Oder versteh ich Dich da grad falsch? :roll:
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

hii,


danke für deine hilfe.
ich habe das jetzt mal versucht mit den 2 events umzusetzen, habe da aber ein kleines problem.
man muss ja beim starten und stoppen beide events gleichzeitig setzen/clearen. das muss also atomar gemacht werden.
und gleichzeitig darf auch die schleife nicht weiterlaufen.
alles müsste also in einen lock.
wenn ich aber die waits in der schelife auch in einen lock mache, kann ich ja nichtmehr stoppen während auf das timeout gewartet wird.


Code: Alles auswählen

import threading


class RU_Timer(threading.Thread):
    def __init__(self, timeout, callback, *cb_args, **cb_kwargs):
        self.timeout = timeout
        self.callback = callback
        self.cb_args = cb_args
        self.cb_kwargs = cb_kwargs
       
        self.timeout_event = threading.Event()
        self.run_event = threading.Event()
        self.running = False
        
        self.lock = threading.Lock()
        
        self.stop_timer()
        
        threading.Thread.__init__(self) 
    
    
    def stop_timer(self):
        if not self.running:
            return False
        else:
            #self.lock.acquire()
            self.running = False
            self.run_event.clear()
            self.timeout_event.set()
            #self.lock.release()
            return True
    
    def start_timer(self):
        if self.running:
            return False
        else:
            #self.lock.acquire()
            self.running = True
            self.timeout_event.clear()
            self.run_event.set()
            #self.lock.release()
            return True
    
    def run(self):
        print "hello"
        while(True):
            #self.lock.acquire()
            self.run_event.wait()
            self.timeout_event.wait(self.timeout)
            if self.run_event.isSet():
                self.stop_timer()
                self.callback(*self.cb_args, **self.cb_kwargs)
            else:
                print "timer stopped"
            #self.lock.release() #das ist ja nicht sinn der sache, so kann ich nicht stoppen
            
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Das geht zum Glück auch ohne Locks, da Eventsetting ja threadsafe ist. Allerdings klappt das nicht mit Deinem Thread-Loop in der derzeitigen Form. Um den zu überarbeiten, müßten wir erstmal klären, was genau Du eigentlich suchst:

Dein Implementationsansatz geht ja eher in Richtung periodischen Timer, während ich unter einem wiederverwendbarem Timer die Möglichkeit verstehe, dem Timer neue Tasks zu geben mit neuem Timeout, hier quasi den Thread wiederverwenden zu können. :?:

zum Code:
Dein Thread terminiert nie, da Du kein break setzt in der while True-Schleife. Wie hier vorzugehen ist, hängt von periodisch || wiederverwendbar ab bzw. solltest Du eine Abbruchvariable testen.

Die Sache mit den callback ist sehr gefährlich, hier mußt Du höllisch aufpassen, was Dein callback macht oder halt locks setzen, diese können aber Dein Timer-Konzept schnell über den Haufen werfen, falls der MainThread und der TimerThread auf die Ausführung bestehen (bis hin zum deadlock). Abhängig davon, was der MainThread in der Zwischenzeit macht bzw. von der Timeraktion erwartet, könnte man das über Queuekonstrukte oder Signaling mit Abarbeiten der Aktion im MainThread lösen. Ist da jetzt auch noch eine Gui im Spiel, müßte man die Sache nochmal anders angehen.

Schreib doch bitte mal genauer, was Du mit den Timer vorhast und welchen Typ Du brauchst.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

jerch hat geschrieben:Das geht zum Glück auch ohne Locks, da Eventsetting ja threadsafe ist.
ja das ist klar. aber ich muss (bei diesem ansatz) sicherstellen, dass beide events in einer atomaren operation gesetzt/gecleared werden. wenn das ginge würde der timer so laufen,.
jerch hat geschrieben: Allerdings klappt das nicht mit Deinem Thread-Loop in der derzeitigen Form. Um den zu überarbeiten, müßten wir erstmal klären, was genau Du eigentlich suchst:
ich brauche den timer für einen timeout mit immer der selben länge.
ich muss den timer also starten können. dann tritt entweder der timeout auf. (der timer legt einen event in in eine queue).
oder ich stoppe den timer, falls das ereignis auf das ich warte innerhalb des timeout aufgetreten ist.
jerch hat geschrieben: zum Code:
Dein Thread terminiert nie, da Du kein break setzt in der while True-Schleife. Wie hier vorzugehen ist, hängt von periodisch || wiederverwendbar ab bzw. solltest Du eine Abbruchvariable testen.
wenn der thread terminiert, kann ich ihn ja nicht wiederverwenden...


ich habe den code jetzt mal geändert und nutze nur noch einen event.
verstehe aber das verhalten nicht so ganz.
wenn ich den timer starte, dann direkt stoppe und dann direkt wieder starte wird nie "timer stopped" ausgegeben.

ist es nicht so, dass die methoden start_timer, stop_timer im main-thread laufen, von da aus die events setzen und im timer-thread dann nur auf die events reagiert wird?
der timer läuft also beim starten in die innere schleife und zählt die ticks.
wird jetzt der run_event gecleared, sollte die innere schleife verlassen werden und die if/else abgearbeitet werden. wird aber nicht.
(anderes problem ist, dass zu erwarten wäre, dass in meinem beispiel fälschlicherweise direkt ein fire kommt, da ich den timer ja direkt wieder aktiviere. wird in der anwendung aber so nie vorkommen. sollte aber trotzdem behoben werden.

output:

start_timer
stop_timer
TICK //warum taucht hier kein "timer stopped" auf?
start_timer
TICK
TICK
TICK
TICK
TICK
TICK
TICK
TICK
TICK
fire

Code: Alles auswählen


import threading
import time

class RU_Timer(threading.Thread):
    def __init__(self, timeout, callback, *cb_args, **cb_kwargs):
        self.timeout = timeout
        self.callback = callback
        self.cb_args = cb_args
        self.cb_kwargs = cb_kwargs
       
        self.run_event = threading.Event()
        
        threading.Thread.__init__(self)
    
    def stop_timer(self):
        if not self.run_event.isSet():
            return False
        else:
            self.run_event.clear()
            return True
    
    def start_timer(self):
        if self.run_event.isSet():
            return False
        else:
            self.run_event.set()
            return True
    
    def run(self):
        while(True):
            ticks = self.timeout
            
            self.run_event.wait()
            while(self.run_event.isSet() and ticks):
                print "TICK"
                ticks -= 1
                time.sleep(0.1)
            
            if self.run_event.isSet():
                print "fire"
                self.run_event.clear()
                #self.callback(*self.cb_args, **self.cb_kwargs)
            
            else:
                print "timer stopped"
            
        


def test(a):
    print a

if __name__ == "__main__":
    
    t = RU_Timer(10, test, "timeout")
    t.start()
    
    print "start_timer"
    t.start_timer()

    print "stop_timer"
    t.stop_timer()

    print "start_timer"
    t.start_timer()
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

wenn der thread terminiert, kann ich ihn ja nicht wiederverwenden...
Das ist richtig, nur so kann Deine Anwendung nicht sauber runterfahren (wenn überhaupt). Du brauchst doch nur auf eine boolsche Variable testen, welche Du vor App-Ende auf False setzt und so dem Thread den Stecker ziehst. Und mit einem join() klappen dann auch evtl. noch anstehende Aufräumarbeiten.
ich brauche den timer für einen timeout mit immer der selben länge.
ich muss den timer also starten können. dann tritt entweder der timeout auf. (der timer legt einen event in in eine queue).
oder ich stoppe den timer, falls das ereignis auf das ich warte innerhalb des timeout aufgetreten ist.
Vllt. ist ja der Event scheduler ausreichend für Dein Problem.
ich habe den code jetzt mal geändert und nutze nur noch einen event.
verstehe aber das verhalten nicht so ganz.
wenn ich den timer starte, dann direkt stoppe und dann direkt wieder starte wird nie "timer stopped" ausgegeben.
Freitexterklärung:
Der Thread hat keine Chance, von der "tickenden Ampel" weg zu kommen, da Du die Ampel zu schnell von grün auf rot stellst und er 0.1s zum Anfahren braucht. ;)

Nee, im Ernst, da der Thread zwischen den Ticks schläft, ist die Wahrscheinlichkeit groß, das er den Zustandswechsel des Events nicht bemerkt, wie Du leicht feststellen kannst, wenn Du die Ticks zählst. Mit einem sleep() zwischen stop und start länger als dem sleep des Threads kannst Du sicherstellen, das er die Kondition wenigstens einmal testet.

Grüße, Jerch

Edit:
ist es nicht so, dass die methoden start_timer, stop_timer im main-thread laufen, von da aus die events setzen und im timer-thread dann nur auf die events reagiert wird?
Ja genau, die Methoden laufen innerhalb des Aufrufkontexts, hier MainThread.
Antworten