Seite 1 von 1
thread vorzeitig beenden?
Verfasst: Mittwoch 15. März 2017, 11:21
von ottto
Hallo,
in meinem Programm hab ich eine zeitaufwendige Abarbeitung in einen extra Thread "ausgelagert", so dass ich meine Oberfläche noch bedienen kann. Mit break komme ich auch vorzeitig aus meiner Schleife und aus dem entsprechendem Thread raus. Dabei muss ich aber warten bis die Zeit des aktuellen Schleifendurchlaufs runter gelaufen ist. Weiterhin bekomme ich eine "RuntimeError: main thread is not in main loop" wenn ich das HauptFenster beende (Beenden-Button) bevor der Thread beendet ist. Gibt es eine Möglichkeit den Thread vorzeitig und sauber zu verlassen?
Danke.
Gruß.
ottto
Code: Alles auswählen
import tkinter as tk
import os, glob, time
import threading
class Thread1GUI:
def __init__(self, master):
self.master=master
master.title("+++ test thread +++")
self.start = tk.Button(master, text=" Ausgabe ! ", command = self.start_click)
self.start.grid(row=0, column=0)
self.ausgabe = tk.Label(master)
self.ausgabe.grid(row=1, column=0)
self.abbrechen = tk.Button(master, text=" Abbrechen ! ", command = self.abbrechen_click)
self.abbrechen.grid(row=2, column=0)
self.beenden = tk.Button(master, text=" Beenden ! ", command = master.quit)
self.beenden.grid(row=3, column=0)
self.mein_break = False
def start_click(self):
def thread_verarbeitung():
for i in range(6):
if self.mein_break == True:
self.ausgabe.config(text="Abbruch !!!")
break
print(i)
self.text=i
self.ausgabe.config(text=self.text)
time.sleep(5)
t1 = threading.Thread(target=thread_verarbeitung)
t1.start()
print("t1 Fertig!")
def abbrechen_click(self):
self.mein_break = True
def main():
root = tk.Tk()
mein_Thread1GUI = Thread1GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 15. März 2017, 11:57
von __deets__
Du hast zwei Probleme:
- du hast blockierende Aufrufe in deinem Worker-Thread
- dein Worker-Thread manipuliert GUI-Objekte.
Ersteres ist nicht zu aendern. Threads zu terminieren ist OS-spezifisch, und kann den Prozess in einem unbekannten Zustand hinterlassen, der dann zu Folgefehlern fuehrt. Du musst also entweder kollaborativ sein, und oft genug in deinem Thread pruefen, ob der sich beenden soll, oder einen externen Prozess nutzen.
Und zweiteres ist streng verboten, weil es undefiniertes Verhalten ist & zu abstuerzen fuehren kann. Andere GUI-Toolkits erlauben da etwas elegantere Loesungen (zB Qt mit inter-thread-signalen), aber bei Tk bleibt dir nur, einen Timer im Main-Thread anzulegen (mit after), und dort regelmaessig zu pruefen, ob der Thread noch laeuft.
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 15. März 2017, 14:02
von ottto
Vielen Dank für die schnelle Hilfe!
Ich hab den Timer zerteilt (Zeile 31). Damit komme ich im Sekundentakt aus der Scleife raus. Das würde mir so reichen. Den Beenden-Button hab ich, so lange der Thread läuft, deaktiviert (Zeile 23 u. 35). Man kann aber immer noch das Fenster selbst schließen. Also mit dem x. Kann man das auch unterbinden?
Danke.
Gruß.
ottto
Code: Alles auswählen
import tkinter as tk
import os, glob, time
import threading
class Thread1GUI:
def __init__(self, master):
self.master=master
master.title("+++ test thread +++")
self.start = tk.Button(master, text=" Ausgabe ! ", command = self.start_click)
self.start.grid(row=0, column=0)
self.ausgabe = tk.Label(master)
self.ausgabe.grid(row=1, column=0)
self.abbrechen = tk.Button(master, text=" Abbrechen ! ", command = self.abbrechen_click)
self.abbrechen.grid(row=2, column=0)
self.beenden = tk.Button(master, text=" Beenden ! ", command = master.quit)
self.beenden.grid(row=3, column=0)
self.mein_break = False
def start_click(self):
def thread_verarbeitung():
self.beenden.config(state=tk.DISABLED)
for i in range(6):
if self.thread_abbrechen():
break
print(i)
self.text=i
self.ausgabe.config(text=self.text)
#time.sleep(5)
for sekunde in range(5):
if self.thread_abbrechen():
break
time.sleep(1)
self.beenden.config(state=tk.NORMAL)
t1 = threading.Thread(target=thread_verarbeitung)
t1.start()
print("t1 Fertig!")
def abbrechen_click(self):
self.mein_break = True
def thread_abbrechen(self):
if self.mein_break == True:
self.ausgabe.config(text="Abbruch !!!")
return True
else:
return False
def main():
root = tk.Tk()
mein_Thread1GUI = Thread1GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 15. März 2017, 14:24
von Sirius3
@ottto: wie schon __deets__ geschrieben hat, darf man aus einem Thread heraus nicht die GUI ändern. Variablen threadübergreifend zu benutzen ist auch nicht gut. Statt des mein_break-Flags solltest Du Events nehmen.
Re: thread vorzeitig beenden?
Verfasst: Freitag 17. März 2017, 08:36
von ottto
Hallo zusammen,
ich versuche gerade das Prog so zu ändern, dass die GUI nicht mehr aus dem Worker-Thread heraus manipuliert wird. Mein Ansatz dazu ist jetzt nur den zeitaufwendigen Teil in je einen Worker-Thread auszulagern. Es gelingt mir aber leider nicht, die Worker-Threads hintereinander und nicht nebeneinander ablaufen zu lassen. Was ich hier mit den Print-Anweisungen simuliere, ist im eigentlichen Programm je ein Datei-rename.
(Mir ist klar, dass "hintereinander" irgendwie am Sinn vom threading vorbei ist.... )
Durch meinen after-timer (Z.33) in thread_timer() sollte der Folge-Thread warten bis der Vorgänger-Thread fertig ist. Kann mir mal jemand mitteilen wo hier mein Denkfehler liegt?
Danke.
Gruß.
ottto
Code: Alles auswählen
import tkinter as tk
import os, glob, time
import threading
class Thread1GUI:
def __init__(self, master):
self.master=master
master.title("+++ test thread +++")
self.start = tk.Button(master, text=" Ausgabe ! ", command = self.start_click)
self.start.grid(row=0, column=0)
self.ausgabe = tk.Label(master)
self.ausgabe.grid(row=1, column=0)
self.abbrechen = tk.Button(master, text=" Abbrechen ! ", command = self.abbrechen_click)
self.abbrechen.grid(row=2, column=0)
self.beenden = tk.Button(master, text=" Beenden ! ", command = master.quit)
self.beenden.grid(row=3, column=0)
self.mein_break = False
self.thread_fertig = True
def start_click(self):
def worker_thread():
self.thread_fertig = False
mein_i = i
print("Anfang Thread " + str(mein_i))
time.sleep(3)
print("Ende Thread" + str(mein_i))
self.thread_fertig = True
def thread_timer():
if self.thread_fertig:
return
self.master.after(500, thread_timer)
print("Timer")
self.beenden.config(state=tk.DISABLED)
for i in range(6):
if self.thread_abbrechen():
break
t1 = threading.Thread(target=worker_thread)
t1.start()
thread_timer()
self.text=i
self.ausgabe.config(text=self.text)
self.beenden.config(state=tk.NORMAL)
def abbrechen_click(self):
self.mein_break = True
def thread_abbrechen(self):
if self.mein_break == True:
self.ausgabe.config(text="Abbruch !!!")
return True
else:
return False
def main():
root = tk.Tk()
mein_Thread1GUI = Thread1GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Re: thread vorzeitig beenden?
Verfasst: Freitag 17. März 2017, 16:01
von __deets__
Da ist so manches Quer. Zum einen startest du einen thread_timer per Thread. Statt einen. Dann startest du schnell mehrere Threads hintereinander, die gleichzeitig auf der gleichen Variable thread_fertig rumhaemmern. Und nirgendwo steht etwas, das verhindert, das der naechste Thread gestartet wird. Woher kommt also deinen Annahme, das waere irgendwie anders?
Wenn du eh sequentiell Tasks abarbeiten willst, nimm
- einen Thread.
- eine Queue.
- stecke Arbeitsauftraege in die Queue (durch den Button-Handler zB).
- der thread wartet mit queue.get() auf einen neuen Auftrag.
- arbeitet den ab, und kann dann pruefen, ob neue Auftraege oder ein Abbruchkriterium vorliegen.
Code: Alles auswählen
import time
import Queue
import threading
def worker(queue):
while True:
task = queue.get() # blockiert bis etwas kommt
if task is None:
break # Ende durch None
else:
print "working"
task()
def main():
tasks = Queue.Queue()
t = threading.Thread(target=worker, (tasks,))
t.daemon = True
t.start()
for i in xrange(5):
tasks.put(lambda: time.sleep(1.0))
tasks.put(None)
t.join()
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 29. März 2017, 11:17
von ottto
Vielen Dank für die Hilfe.
Wie beschrieben werden die Queue-Aufträge abgearbeitet. Leider wird mein Fenster bei der Abarbeitung weiterhin blockiert. Der Abbrechen-Button läst sich erst betätigen wenn die Queue abgearbeitet ist.
Code: Alles auswählen
import tkinter as tk
import os, glob, time
from queue import Queue
from threading import Thread
class Thread1GUI:
def __init__(self, master):
self.master=master
master.title("+++ test thread +++")
self.start = tk.Button(master, text=" Ausgabe ! ", command = self.start_click)
self.start.grid(row=0, column=0)
self.ausgabe = tk.Label(master)
self.ausgabe.grid(row=1, column=0)
self.abbrechen = tk.Button(master, text=" Abbrechen ! ", command = self.abbrechen_click)
self.abbrechen.grid(row=2, column=0)
self.beenden = tk.Button(master, text=" Beenden ! ", command = master.quit)
self.beenden.grid(row=3, column=0)
def start_click(self):
def worker(q):
while True:
task = q.get()
if task is None:
break
else:
print(str(q.qsize()))
q.task_done()
task()
self.ausgabe.config(text = "Ausgabe - Button")
tasks = Queue()
t = Thread(target=worker, args=(tasks,))
t.Daemon = True
t.start()
for i in range(20):
tasks.put(lambda: time.sleep(0.5))
tasks.put(None)
t.join()
def abbrechen_click(self):
self.ausgabe.config(text = "Abbrechen - Button")
def main():
root = tk.Tk()
mein_Thread1GUI = Thread1GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 29. März 2017, 12:09
von Sirius3
@otto: was erwartest Du, wenn Du t.join() aufrufst?
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 29. März 2017, 12:12
von BlackJack
@ottto: `Thread.join()` wartet bis der Thread beendet ist. Dann hättest Du keinen Thread starten brauchen wenn Du nach dem Starten nicht weitermachst, sondern wartest bis der fertig ist.
Re: thread vorzeitig beenden?
Verfasst: Mittwoch 29. März 2017, 12:33
von ottto
so einfach ....
funktioniert.
Danke Euch.