exit thread

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
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Hallo,
ich habe ein Problem mit meinem thread: dieser bedient in "definierten" Intervallen einen COM-port und tut dies auch dann noch wenn der port bereits geschlossen wurde. Das ist dann natürlich nicht mehr so schön wie vorher.
Den Code habe ich reduziert, dh es passiert noch etwas mehr, aber ich hoffe alles Wesentliche für mein Problem hier extrahiert zu haben.

Code: Alles auswählen

class SerialThread(threading.Thread):
    def __init__(self, Prm): 
        self.Prm = Prm
        self.trace = True
        self.lock = threading.Lock()
        self.clientEvent = threading.Event()
        threading.Thread.__init__(self)

    def run(self):  
        try:
            self.client = serial.Serial(  port = self.Prm['port'], 
                                          baudrate = int(self.Prm['baudrate']), 
                                          bytesize = int(self.Prm['bytesize']), 
                                          parity = self.Prm['parity'], 
                                          stopbits = int(self.Prm['stopbits']),
                                          timeout = int(self.Prm['timeout']))
        except:
            PyMsgBox.PopUpMsg().dispConfirm("Cannot open serial port " + self.Prm['port'], "Error", 0)  
            
        self.clientEvent.set()
             
        while self.trace:
            self.lock.acquire()
            self.client.write(READ + "\r\n")
            time.sleep(19)
            line = self.client.readline()
            # ... verarbeitet die Zeile
            self.lock.release()

            time.sleep(float(self.Prm['update']))

    def stop(self):
        self.trace = False
        self.client.close()

def Init(...):
    Prm = {'update' : '20'}
    
    thread = SerialThread(Prm)
    thread.start()
    while not thread.clientEvent.is_set():
        pass	
    return thread

def End(...):
    RetDict = thread.stop()


# ... hier werden Init aufgerufen, andere Dinge getan und dann End ausgeführt
cmax
User
Beiträge: 14
Registriert: Dienstag 22. Januar 2013, 21:36

hypnoticum hat geschrieben:... und tut dies auch dann noch wenn der port bereits geschlossen wurde.
Durch den Aufruf von self.client.close() in stop()? Vll. gnügt es schon diesen Aufruf hinter die while-Schleife in run() zu verlegen?
BlackJack

@hypnoticum: Wo wird das `SerialThread.lock` denn noch benutzt? So wie es bis jetzt da (nicht) verwendet wird, macht das nicht viel Sinn.

Und der Sinn von `Event`\s ist es Schleifen die einfach nur sinnlos die CPU mit nichtstun beschäftigen, zu vermeiden. Darum haben die eine `wait()`-Funktion.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@hypnoticum: Dein Code enthält mehrere Fehler bzw. Stellen, die man verbessern könnte.

1. Die Ursache dafür, dass die run()-Methode deines Threads auf den bereits geschlossen COM-Port zugreift, liegt darin, dass du den Port zu früh schließt. Die stop()-Methode sollte lediglich ein flag setzen, das anzeigt, dass der Port geschlossen werden sollte, aber dies nicht selbst tun. In deinem Fall wäre dies das flag self.trace, das du ja bereits auf True setzt. Daraufhin wird in der run()-Methode die Abbruchbedingung der Schleife erreicht, und diese verlassen, und danach sollten der Port geschlossen und der Thread beendet werden.

2. Locks sollte man mittels des with-Statements verwalten, statt explizit acquire() und release() darauf auszuführen.

3. Mir ist nicht klar, wozu du überhaupt ein Lock brauchst. Wenn es dazu dient, den Zugriff auf den COM-Port zu regeln, ist es an der falschen Stelle, da du ja nicht verhinderst, dass man das Lock umgehen und direkt auf thread.client zugreifen kann. Falls du dagegen weißt, dass niemand sonst direkt auf thread.client zugreift, brauchst du kein Lock.

4. So, wie du threading.Event verwendest, funktionierst du deinen Rechner zur Heizung um. Statt in einer leeren Schleife immer wieder selbst zu testen, ob der Event gesetzt wurde, solltest du die dafür vorgesehene Methode der Event-Klasse verwenden, dh. Event.wait().

5. Wenn der Versuch, den COM-Port zu öffnen, misslingt, zeigst du zwar eine Messagebox, tust aber danach so, als wäre nichts passiert. An dieser Stelle sollte der Thread abbrechen, statt zu versuchen, mit dem ungeöffneten Port zu kommunizieren.

Vieles an deinem Code ist mir nicht klar, aber soweit ich sehen kann, ist der Zweck des ganzen unfgefähr sowas (Pseudocode):

Code: Alles auswählen

should_read = True

...
run_async:
    while should_read:
        data = port.read()
        do_something_with(data)

...

if some_condition:
    should_read = False
Wichtig wäre zu wissen, ob die gelesenen Daten nur innerhalb des asynchron laufenden Threads verarbeitet werden (dann brauchst du kein Lock) oder im Hauptprogramm verwendet werden sollen. In letzterem Fall wäre es vielleicht besser, gleich Queue.Queue zu verwenden.

Im ersten Fall wäre meine Lösung ungefähr sowas:

Code: Alles auswählen

def serial_thread(config, init, stop):
    try:
        port = serial.Serial(port=self.config['port'],
                             baudrate=int(self.config['baudrate']),
                             bytesize=int(self.config['bytesize']),
                             parity=self.config['parity'],
                             stopbits=int(self.config['stopbits']),
                             timeout=int(self.config['timeout']))
    except:
        PyMsgBox.PopUpMsg().dispConfirm(
            "Cannot open serial port " + config['port'], "Error", 0)
        return
    init.set()
    while not stop.is_set():
        port.write(READ + "\r\n")
        time.sleep(19)
        line = port.readline()
        # ... verarbeitet die Zeile
        time.sleep(float(self.config['update']))
    port.close()

def Init():
    Prm = {'update' : '20'}
    init = threading.Event()
    stop = threading.Event()
    thread = threading.Thread(target=serial_thread, args=(Prm, init, stop))
    thread.start()
    init.wait()
    return stop

def End(stop):
    stop.set()

...

stop = Init()
do_stuff()
End(stop)
Hier sieht man auch die zwei Verwendungsweisen von threading.Event. Bei init möchte man anhalten, bis ein bestimmter Zustand eingetreten ist (dh: die Initialisierung ist vollständig), bei stop möchte man solange weitermachen, bis ein bestimmter Zustand nicht mehr gegeben ist (d.i. der Mach-Weiter-Zustand).
In specifications, Murphy's Law supersedes Ohm's.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

@pillmuncher:
Dein Code ist genau das was ich vorhatte :). Auch Dir nochmal vielen Dank für deine ausführlichen Erklärungen - das hilft mir wirklich weiter.
Das lock hatte ich verwendet, da ich noch weitere Zugriffe auf den port habe und damit den zyklischen Zugriff sozusagen "atomic" machen wollte. Ich schau mir das nochmal genau an ...
Die Fehlerbehandlung ist deshalb etwas nachlässig, da ich sowieso mein Programm immer neu starten muss, wenn etwas Unvorhergesehenes passiert. Wie zum Beispiel, dass der port nicht geöffnet werden kann, den ich aber unbedingt brauche. Ist alles andere als schön - weiss ich auch. Mir reicht es aber erstmal zu wissen was passiert ist.
Antworten