Threads um Endlosschleife abzubrechen?

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
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

Hallo,
dank der Hilfe hier im Forum habe ich ein Tkinter Programm, dass den print Befehl in einer Endlosschleife ausführt mit einem Button zum Stoppen der Schleife (http://www.python-forum.de/viewtopic.php?f=18&t=36170). Jetzt möchte ich in der Schleife ein externes Gerät steuern, was einige Minuten dauert und auch währenddessen die Schleife abbrechen können. Sollte ich das mit 2 Threads machen oder gibt es da einen bessere Lösung? Hier https://gist.github.com/zed/4067619wurde ein ähnliches Problem z.B. mit subprocess gelöst
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Threads zu nutzen wäre durchaus sinnvoll. Bei der Verwendung von Threads würdest du einen Thread haben indem du mit dem Gerät kommunizierst, diesem Thread würdest du über eine Queue Nachrichten schicken wie z.B. abbrechen und du würdest after bzw. after_idle nutzen um eine Methode im GUI Thread mit einer Nachricht aufzurufen die auf die dann mit Veränderungen an der GUI reagiert werden kann.
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

Danke für deinen Beitrag. Zum testen habe ich mal diesen Thread genommen:

Code: Alles auswählen

class sleepThread(threading.Thread):
    
    def run(self):
	i = 1
	while True:
            print (i)
            time.sleep(15)
            i = i+1
Das Fenster lässt sich bedienen während die Schleife läuft. Aber leider kriege ich das Abbrechen der Schleife nicht hin. Wenn der Stop Button gedrückt wird sollte sich der Thread beenden, auch wenn er grade time.sleep ausführt. Könnt Ihr mir einen Tipp geben was ich machen muss?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@semper27: dazu müssen ja zwei Threads miteinander kommunizieren, dazu gibt es verschiedene Klassen in threading. Für Deinen Fall würde sich "threading.Event" mit seiner wait-Methode anbieten.
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

In der Dokumentation von threading.Event wird beschrieben wie ein Thread wartet bis ein Event auftritt. Ich möchte aber das der Thread beendet wird wenn ein Event auftritt, oder verstehe ich da etwas falsch?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@semper27: Du willst doch warten, bis ein Event auftritt, um dann die Schleife zu beenden.
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

Ich habe die Steuerung des Geräts jetzt in viele Einzelschritte zerlegt und vor jedem die Abbruchbedinung geprüft. Danke für die Beiträge.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@semper27: das hört sich nach keiner guten Idee an.
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

Oh, deinen letzten Beitrag hatte ich nicht mehr gelesen. Ist zwar schon eine Weile her und der Programmteil funktioniert auch ganz gut, aber fals noch jemand einen Tipp hat was man es besser programmieren kann würde ich mich freuen. Hier der Code:

Code: Alles auswählen

from functools import partial
import ScrolledText
import serial
import threading
import time
import tkFileDialog
import Tkinter as tk

class ProgrammThread(threading.Thread):

        def __init__(self, masetr=None):
                threading.Thread.__init__(self)
                self.stop_signal = False

        def run(self):
                k = 0
                while True:
                        if self.stop_signal == False:
                                #hier wird in jedem Durchlauf ein anderer Befehl ausgeführt
                                if k == 10: break
                                k = k+1
                main_frame.stop() 

class MainFrame(tk.Frame):

        def __init__(self, master=None):
                tk.Frame.__init__(self, master)

                self.start_button = tk.Button(self, text='Skript Starten', relief='flat', command=self.start)
                self.start_button.grid(row=0, column=4, sticky='W')

                self.stop_button = tk.Button(self, text='Stop', state=tk.DISABLED, relief='flat', command=self.stop)
                self.stop_button.grid(row=0, column=5, sticky='W')

        def start(self):
                self.start_button['state'] = tk.DISABLED
                self.stop_button['state'] = tk.NORMAL
                self.programm = ProgrammThread()
                self.programm.setDaemon(True)
                self.programm.start()

        def stop(self):
                self.start_button['state'] = tk.NORMAL
                self.stop_button['state'] = tk.DISABLED
                self.programm.stop_signal = True

if __name__ == '__main__':
        root = tk.Tk()
        main_frame = MainFrame(root)
        main_frame.pack()
        root.mainloop()
Das beim Drücken von Start noch jedesmal ein neuer Thread erstellt wird wollte ich noch ändern.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@semper27: Zeile 19 wäre ja das Interessante. stop_signal sollte nie etwas anderes sein als False, weil Du sonst eine Endlosschleife hast. So wie es jetzt geschrieben ist, ist es nur eine komplizierte for-Schleife:

Code: Alles auswählen

    def run(self):
        for k in range(11):
            if self.stop_signal:
                break
            do_something()
Aus Threads heraus darf die GUI nicht verändert werden, Du mußt also innerhalb der GUI per after regelmäßig abfragen, ob der Thread noch läuft; außerdem ist main_frame eine globale Variable, die nicht existieren sollte; die Zeilen in 'if ... __main__' gehören in eine Funktion, die üblicherweise main heißt. Eingerückt wird mit 4 Leerzeichen.
BlackJack

@semper27: `ProgrammThread.__init__` hat ein `masetr`-Argument, das überhaupt nicht verwendet wird.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from threading import Thread
from time import sleep


class ProgramThread(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.stop_signal = False

    def run(self):
        for i in xrange(11):
            if self.stop_signal:
                break
            print(i)
            sleep(1)


class MainFrame(tk.Frame):

    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.thread = None
        self.start_button = tk.Button(
            self, text='Skript Starten', command=self.start
        )
        self.start_button.pack(side=tk.LEFT)
        self.stop_button = tk.Button(
            self, text='Stop', state=tk.DISABLED, command=self.stop
        )
        self.stop_button.pack(side=tk.LEFT)

    def _check_thread(self):
        if self.thread.is_alive():
            self.after(500, self._check_thread)
        else:
            self.start_button['state'] = tk.NORMAL
            self.stop_button['state'] = tk.DISABLED
            self.thread = None

    def start(self):
        assert self.thread is None
        self.start_button['state'] = tk.DISABLED
        self.stop_button['state'] = tk.NORMAL
        self.thread = ProgramThread()
        self.thread.daemon = True
        self.thread.start()
        self._check_thread()

    def stop(self):
        assert self.thread is not None
        self.thread.stop_signal = True


def main():
    root = tk.Tk()
    main_frame = MainFrame(root)
    main_frame.pack()
    root.mainloop()
    

if __name__ == '__main__':
    main()
semper27
User
Beiträge: 14
Registriert: Freitag 24. April 2015, 16:14

Vielen Dank euch beiden für die Tips und den Code! Jetzt verstehe ich Threads mit GUI besser, und das Programm läuft auch flüssig wenn oft Start/Stop gedrückt wird :)
Antworten