Live Plot mit threading

Fragen zu Tkinter.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Hi,

ich bastel gerade an einem Programm das Messwerte von einem USB-Gerät einließt und sie parallel in einem Liveplot darstellen soll, das ganze soll über ein Tkinter-GUI haben. Ziel dabei ist eine möglichst hohe (und konstante) Datenrate, weshalb ich probiere den Liveplot in eine anderen Thread auszulagern.

Den Datenabruf realisiere über eine Konstruktion die etwa so aussieht:

Code: Alles auswählen

def get_data():
	if run == True:
       		[...........]							#Code zum Abruf der Messdaten
       		root.after(0,get_data)
Für den Liveplot habe ich zwei Versionen getestet.
Ich bin allerdings mit beiden nicht wirklich zufrieden, weil zum einen immer noch meine Datenrate schwanken lassen und zum anderen nach einer Gewissen Laufzeit immer Abstürzen und folgenden Fehler liefern:
Tcl_AsyncDelete: async handler deleted by the wrong thread
In der ersten Version wird jedes mal wenn der Plot aktualisiert wird ein neuer Thread geöffnet, das sieht etwa so aus.

Code: Alles auswählen

import _thread

def plot_graph():
	if run == True:
		[........] 							#Code zum Plotten der Messdaten
		root.after(100,open_plot_thread)
		_thread.exit()

def open_plot_thread():
	 _thread.start_new_thread(plot_graph,())
In der zweiten startet eine While Schleife in einem Neuen Thread. Und sieht etwa so aus:

Code: Alles auswählen

import _thread

def plot_graph():
	[........] 							#Code zum Plotten der Messdaten

def open_plot_thread():
	 _thread.start_new_thread(plot_while,())

def plot_while():
	while run == True:
		plot_graph()
        	time.sleep(0.1)	 
  
Gibt es irgend eine bessere Möglichkeit mein Ziel zu erreichen?
Oder sollte ich besser das Programm in zwei splitten und eins zum Abruf und Speichern der Daten nutzen und die Gespeicherten Daten dann Parallel von einem Zweiten Programm live plotten lasen?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@GMAch: Auf jeden Fall falsch ist schon mal das `_thread`-Modul, wo man ja schon am Namen sieht, das man das nicht verwenden soll. Ein führender Unterstrich kennzeichnet Namen die nicht zur öffentlichen API gehören. Für Threads gibt es das `threading`-Modul.

Und aus einem anderen Thread plotten heisst hoffentlich nicht, dass Du dort die GUI veränderst‽ Das geht nicht, und dürfte die Ursache für die Abstürze sein. Die GUI darf nur aus dem Thread verändert werden, in dem die GUI-Hauptschleife läuft.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

__blackjack__ hat geschrieben: Montag 24. September 2018, 11:47Und aus einem anderen Thread plotten heisst hoffentlich nicht, dass Du dort die GUI veränderst‽ Das geht nicht, und dürfte die Ursache für die Abstürze sein. Die GUI darf nur aus dem Thread verändert werden, in dem die GUI-Hauptschleife läuft.
Was ich genau mache ist das zuerst einen Dummy-Plot Matplotlib erstelle, und den in der Hauptschleife mit "FigureCanvasTkAgg()" in der GUI packe.
Was ich dann mit der Funktion in dem Nebenthread mache ist .clear() auf den Plot anzuwenden, dann mit .plot() neue Kurven rein plotte, die die x und y Limits der Achsen neu setze und anschließen mit .drow() das Canvas in der Oberfläche neu zeichne.

Gehen tut das jedenfalls, das Programm läuft ja schon für ca. eine Minute (in der es auch den Graph updated) befor der Fehler auftritt.


Das mit dem `threading`-Modul muss ich mir noch mal ansehen.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Hi,

ich hab er nochmal mit `threading` probiert aber ich bekomme den selben Fehler, kann mir jemand sagen was diese Fehlermeldung genau bedeutet?


Der Code sieht jetzt so aus:

Code: Alles auswählen

import _thread

def plot_graph():
	[........] 							#Code zum Plotten der Messdaten


def open_plot_thread():
	t = threading.Thread(target=plot_while)
	t.start()

def plot_while():
	while run == True:
		plot_graph()
        	time.sleep(0.1)	 
  
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@GMAch: Das geht nicht! Genau das bedeutet die Fehlermeldung. Bei nebenläufiger Programmierung kannst Du nicht einfach sagen das das Programm geht, nur weil es ein paar Minuten gelaufen ist. Das bedeutet nur das es halt noch nicht in den Fehler gelaufen ist. Fehler sind da nicht mehr deterministisch, die treten dann nicht mehr immer zur gleichen Zeit im Programmablauf auf, sondern irgendwann. Vielleicht auch gar nicht. Also so lange bis sie dann halt doch auftreten. Testläufe sind nicht genug, man muss wissen was man da tut und welche Probleme es geben kann.

Der Fehler ist das Du in dem anderen Thread etwas an der GUI bastelst. Das darfst Du schlicht nicht machen. Sonst passieren komische Dinge. Zum Beispiel Programmabstürze.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

OK unter "geht nicht" verstehe ich in der Regel, dass das Programm überhaupt nicht läuft.

Das heißt wenn ich das Programm zum laufen bekommen will muss ich es quasi umbauen und die Datenabfrage in den Thread packen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Datenabfrage kann im Thread passieren, ja. Und die Daten im GUI-Loop per regelmaessigem timer durch die "after"-Funktion geplottet werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@GMAch: Geht nicht heisst das kann man so nicht machen wenn man will das das Programm zuverlässig läuft. Ob das nun sofort abstürzt, oder nach x Minuten, oder gar nicht, was aber nicht garantiert ist, ändert nichts daran das das so nicht geht. Von einem Programm das geht erwartet der Benutzer allgemein das es *immer* geht. :-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Ich hätte da noch eine Frage, ist multiprocessing eine Option für das was ich vorhabe?

Und wenn ja worauf muss ich dabei achten um Probleme zu vermeiden?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, ist keine Option. Jedenfalls nicht fuer das Plotten. Berechnungen auf deinen Daten die du durchfuehrst bevor du plottest - kann sein, kommt auf die Umstaende an.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Ich hab gesehen, dass beim Multiprocessing die Prozesse nicht wie beim Threading gemeinsame Globale Variablen nutzen können (zu mindestens nicht über das übliche "global"), ist dass generell so oder gibt es da irgendeine Option extra für Multiprocessing?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@GMAch: das geht nicht. Auch bei Threads sollte man nicht mit globalen Variablen arbeiten, weil das zu unvorhersagbarem Verhalten führen kann. Auch in normalen Programmen sollte man nicht mit globalen Variablen arbeiten, weil das Verstehen und finden von Fehlern sehr kompliziert macht.
Threads und Multiprocessing-Prozesse kommunizieren über spezielle Datenstrukturen (Queues, Events, etc.).
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

@GMach: nachdem ich nochmal in einem deiner aelteren Threads gesehen habe, worum es dir eigentlich geht: multiprocessing hat wie schon erwaehnt keine positivine Effekte fuer das Plotten. Aber fuer deinen Anwendungsfall ist es ggf. trotzdem angezeigt, weil du ja eine konstante Datenrate haben moechtest. Und das ist mit Python und seinem GIL bei Threads nicht notwendigerweise zu garantieren (im Rahmen in dem sowas ueberhaupt garantiert werden kann auf einem nicht-echtzeit-OS). Mit multiprocessing bekommst du dann halt eine Pipe/Queue, mit der du die Messwerte + Zeitstempel aus dem Kindprozess entgegen nehmen kannst.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

@_deets_

Ich hab das Therading jetzt übrigens mit Plotten im Hauptprogramm und Datenabruf im Thread jetzt übrigens stabil zum laufen bekommen danke für die Erklärung was das Problem war.

Ich glaub das mit dem Multiprocessing lass ich erstmal außen vor, das wird mir dann doch etwas zu aufwendig. Wenn das ohne keine ausreichende Performance liefert, pack ich Datenabruf und Plotten halt in verschiedene Programme und notfalls gehts sonst auch ohne GUI.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Ich tendiere momentan immer mehr zu einer Lösung ohne GUI, da das eine deutlich höhere und stabilere Datenrate ermöglicht.
Ich habe jetzt quasi zwei Pythonscripte ohne GUI.

Mit Script 1 rufe ich die Daten ab und Speiche sie in ein .txt file.

In Script 2 nutze ich matplotlib.animation.FuncAnimation um einen Liveplot der Daten im .txt zu erzeugen.

Was ich mich jetzt Frage: gibt es irgendeine Möglichkeit Script 2 von Script 1 in einer neuen Instans von Python aus zu starten und ihm den Dateinamen des .txt files als Paramater zu übergeben?
Das "subprocess"-Modul scheint sowas zu machen, aber mir ist aus den Beispielen die bisher online gesehen hab nicht so 100% klar geworden wie der Code dafür aussehen müsste.
Kann mir das jemand erklären?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist ein seltsames Vorgehen. Wenn du das wirklich so willst, also *IMMER* Datenakquise im eigenen Prozess, und dann eine zweite GUI - dann mach das mit multiprocessing, und von der GUI aus starte einen Workeprozess, der die Daten liest & wegschreibt. Wobei der die dann auch nicht nur in die text-Datei schreiben, sondern eben per Pipe auch gleich an den Hauptprozess kommunizieren kann, so das der dann die Daten darstellt.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@GMAch: mehr als ein Beispiel kann hier aber auch niemand geben:

Code: Alles auswählen

script1 = subprocess.Popen(["script1.py", filename])
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

Sirius3 hat geschrieben: Mittwoch 10. Oktober 2018, 17:34 @GMAch: mehr als ein Beispiel kann hier aber auch niemand geben:

Code: Alles auswählen

script1 = subprocess.Popen(["script1.py", filename])
Und was muss ich in "script 1" machen damit es den filename einließt? Das ist der Punkt zu dem ich bisher nicht finden konnte.
GMAch
User
Beiträge: 26
Registriert: Dienstag 4. September 2018, 07:32

__deets__ hat geschrieben: Mittwoch 10. Oktober 2018, 16:31 Das ist ein seltsames Vorgehen. Wenn du das wirklich so willst, also *IMMER* Datenakquise im eigenen Prozess, und dann eine zweite GUI - dann mach das mit multiprocessing, und von der GUI aus starte einen Workeprozess, der die Daten liest & wegschreibt. Wobei der die dann auch nicht nur in die text-Datei schreiben, sondern eben per Pipe auch gleich an den Hauptprozess kommunizieren kann, so das der dann die Daten darstellt.
Ich muss ehrlich sagen das ich noch nicht so richtig durch blicke wie ich das mit der Pipe funktioniert.

Speichern muss ich die Daten zudem ohnehin, und die Daten immer in eine Matrix rein zuscheiben schien sich auch nicht gerade positiv auf die Performace des Programms auszuwirken.

Ziel des ganzen ist eh die Leistungsfähigkeit des Detektors zu untersuchen, daher ist Nutzerfreundlichkeit erstmal zweitrangig.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Argumente bekommt man über sys.argv:

Code: Alles auswählen

filename = sys.argv[1]
Antworten