GTK und Threads / Threading, Gobject !?

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Trundle hat geschrieben:Es gibt die Dokumentation von GDK, die klar sagt, dass man unter Windows besser nicht versuchen sollte, von mehreren Threads aus auf GDK-Funktionen zuzugreifen.
Ja richtig, habe ich so auch gesagt.
Er will in einem Thread Daten aktualisieren und falls sich was ändert, will er diese in der GUI anzeigen bzw. updaten. Sehe da keine Notwendigkeit in mehreren Threads auf die GUI bzw. auf GTK zuzugreifen. Das Absetzen eines Signals aus einem Thread ist ja kein direkter Zugriff auf die GUI.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

ice2k3 hat geschrieben:
Trundle hat geschrieben:Es gibt die Dokumentation von GDK, die klar sagt, dass man unter Windows besser nicht versuchen sollte, von mehreren Threads aus auf GDK-Funktionen zuzugreifen.
Ja richtig, habe ich so auch gesagt.
Dann verstehe ich dein Posting einfach nicht. Du schreibst etwas von Locks, warum aber Locks, wenn man doch nicht auf die GUI zugreift? Zumal das mit den eigenen Locks wie gesagt nicht funktioniert.
ice2k3 hat geschrieben:Er will in einem Thread Daten aktualisieren und falls sich was ändert, will er diese in der GUI anzeigen bzw. updaten. Sehe da keine Notwendigkeit in mehreren Threads auf die GUI bzw. auf GTK zuzugreifen. Das Absetzen eines Signals aus einem Thread ist ja kein direkter Zugriff auf die GUI.
Dann dürfen die Signal-Handler aber auch nicht auf die GUI zugreifen.

Ich habe mal deinen Logger als Beispiel genommen (das Tray-Icon habe ich entfernt, stattdessen wird das Fenster immer angezeigt und den Code zum Überwachen der Threads habe ich auch entfernt). Einmal mit einem `threading.RLock()`, einmal mit einem Signal. Zumindest auf meiner Maschine produzieren beide Versionen mit an Sicherheit grenzender Wahrscheinlichkeit einen Segfault.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Deine Beispiele sollen ja wohl ein Witz sein oder??
Wenn du einen Textbuffer mit while True Prints überflutest, ist ja klar, dass das nach ein paar Sekunden abkackt.

Folgender leicht modifizierter Code mit den Signalen funktioniert problemlos.
http://paste.pocoo.org/show/107434/

Und dass das zweite Beispiel so nicht funktioniert, ist mir auch klar ohne zu testen. Du greifst aus dem zweiten Thread auf GTK Elemente zu (in dem du den Textbuffer beschreibst). Und genau das soll man ja laut Doku nicht machen :!:

Ich weiß, dass das in meinem alten Beispiel auch so ist, darum habe ich aber erwähnt, dass das mit dem Logger nicht funktioniert. Meine Alternative war, dass ich eine Liste mit den Prints gefüllt habe und die Liste regelmäßig in den Textbuffer geschrieben habe. Und genau die Liste muss dann mit einem Lock geschützt werden. Aber vielleicht setze ich jetzt auch dein Signal-Handler Beispiel ein. Schein echt gut zu funktionieren, wenn man es richtig einsetzt...
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Nein ice2k3, Dein letztes Bsp. crasht mit dem Hinweis eines ungültigen Iterators auf den Textbuffer. Schau Dir bitte mal die Links an, welche ich weiter oben gepostet habe. Hier ist die Rede davon, das der Zugriff von versch. Threads möglich ist, hierfür aber gdk_threads_set_lock_functions() "gefixt" werden muß, um die Locks vollwertig zu erhalten. Desweiteren warnen die gtk-Leute vor dem Benutzen anderer Lockmechanismen als dem Gtk-eigenen, da hier wiederum Seiteneffkte nicht ausgeschlossen sind. So, das gilt auch nur für unixoide Systeme, unter Windows ist das Gtk-Locking gänzlich unbrauchbar.

Was spricht eigentlich gegen meinen Vorschlag, emit() via idle_add() zum MainThread durchtröpfeln zu lassen und so "normale" Signale absetzen zu können?
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Nein, meine Beispiele sind kein Witz. Vielmehr ist deine Aussage, dass dein Code "problemlos" funktioniere, ein Witz. Aber im Grunde bin ich ja froh, denn das war genau das, was ich damit zeigen wollte (ich hab sogar noch überlegt, ob ich das Beispiel auch hinzufüge). Dass dein Code funktioniert, ist reiner Zufall bzw. Glücksspiel. Und genau das meinte ich damit, dass Probleme mit Threads nicht deterministisch auftreten und dass das mit der eigenen praktischen Erfahrung so eine Sache ist.

Dass mein Code wegen überfluten segfaultet, stimmt, nicht aber, dass das klar ist, weil ich den Textbuffer überflute. GTK+ (und nicht nur das, sondern eigentlich jedes Toolkit) verträgt es nicht, wenn man gleichzeitig aus mehreren Threads auf die GUI zugreift. Soweit sind wir uns hoffentlich alle einig. Dadurch, dass ich jetzt - wie du so schön sagst - "überflute", tritt aber mit sehr hoher Wahrscheinlichkeit der Fall ein, dass eben zwei Threads (mein "print-Thread" und eben die GTK+-Hauptschleife) auf die GUI zugreifen. Du hingegen sorgst mit deinem ``sleep(0.1)`` dafür, dass es sehr unwahrscheinlich ist, dass dieser Fall eintritt. Aber er ist keinesfalls ausgeschlossen. Tatsächlich bekomme ich auch mit deinem "problemlos funktionierenden" Code jedes Mal einen Segfault hin, ich muss das eben nur ein wenig provozieren -- indem ich den Text markiere, die Größe vom Fenster ändere, beim TextView scrolle etc.

Dass es tatsächlich an den Threads und nicht am "Überfluten" liegt, lässt sich auch sehr einfach demonstrieren, indem man das ``self.emit('write', text)`` durch ein ``gobject.idle_add(self.emit, 'write', text)`` austauscht.


Edit: @jerch: Dagegen spricht nichts, und ich persönlich würde es wohl auch so lösen.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

jerch hat geschrieben:Was spricht eigentlich gegen meinen Vorschlag, emit() via idle_add() zum MainThread durchtröpfeln zu lassen und so "normale" Signale absetzen zu können?
Genau so habe ich mir das ja vorgestellt.

Aber ganz egal wie ich das Beispiel ändere, die write_handler Methode wird immer von den Threads und nie vom Main-Thread aufgerufen.
Wie müsste das in meinem Beispiel dann konkret aussehen?
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Warum wollt ihr eigentlich unbedingt mit einem Signal arbeiten?
Als "Signal" kann man doch einfach eine Methode der GUI Klasse aufrufen, die auf die GUI nicht zugreift, und die dann eine Methode mit ``gobject.idle_add`` aufruft um die GUI zu ändern.

Folgendes Beispiel funktioniert bei mir:
http://paste.pocoo.org/show/107525/
(Die 2. Methode soll nur demonstrieren, dass das auch mit normalen Methoden funktioniert und nicht nur mit der stdout Umleitung auf die ``write`` Methode)

Edit:
Warum im Thread nicht einfach so??

Code: Alles auswählen

gobject.idle_add(my_gui_class.change_label, new_label)
Der Zwischenschritt über die extra Methode benötigt man ja gar nicht...
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Wie Du das mit dem idle_add() umsetzt, ist doch eine Frage dessen, was Dein Programm letztendlich machen soll.
Wie ich den OP verstanden hatte, braucht er aus dem Thread heraus verschiedene Manipulationsmöglichkeiten an der GUI. Mit dieser Anforderung ist es doch naheliegend, dem Thread Signale beizubringen.
Braucht der Thread hingegen nur eins/zwei Interaktionsmöglichkeiten mit der GUI, bläßt das nur das Programm unnötig auf. Dann rufst Du eben Methoden direkt auf, mußt aber sicherstellen, das in den Methoden keine "globalen" (GUI oder andere) oder beschränkten Ressourcen (Devicezugriff) manipuliert werden oder diese Zugriffe aus dem Thread heraus locken. Gut, das Locking funktioniert nicht in Windows im Gtk-Falle, daher der Umweg über idle_add().
Der Zwischenschritt über die extra Methode benötigt man ja gar nicht...
Die "Zusatzmethode" stellt doch nur eine Art "gateway"-Funktion zur Verfügung, um den idle_add() Aufruf nicht jedesmal neu setzen zu müssen. Du machst es ja auch so mit der Methode insert_text().

Übrigens hat Dein Programm ein designtechnisches Problem:
Schließt man das Fenster, so fährt Gtk runter während der Thread weiterhin versucht, die Ausgabe nach logger() abzusetzen, was auch schön mit einer Exception quittiert wird. Das sollte sich aber mit einem callback lösen lassen.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

jerch hat geschrieben:Dann rufst Du eben Methoden direkt auf, mußt aber sicherstellen, das in den Methoden keine "globalen" (GUI oder andere) oder beschränkten Ressourcen (Devicezugriff) manipuliert werden oder diese Zugriffe aus dem Thread heraus locken.
Aber wenn ich die Methoden über idle_add aufrufe, kann ich ja auf die GUI zugreifen. Die Methode wird ja dann vom Mainthread aufgerufen (wie das Beispiel ja auch zeigt).
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Aber wenn ich die Methoden über idle_add aufrufe, kann ich ja auf die GUI zugreifen. Die Methode wird ja dann vom Mainthread aufgerufen (wie das Beispiel ja auch zeigt).
Nein.
Mit idle_add() greifst Du weder auf die GUI zu noch rufst DU diese Methoden direkt auf. Bildlich gesprochen weist Du gdk an, doch mal bei dieser Methode vorbeizuschauen. Daher dann auch der MainThread-Zugriff.

Das steht in keinem Widerspruch zu meinem Vorpost.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Ja, das ist mir klar.

Aber ich verstehe immer noch nicht, warum Signale eingesetzt werden sollen. Wenn man mit ``idle_add`` eine emit Funktion aufruft, kann man doch gleich die Methode direkt aufrufen, die mit dem Signal aufgerufen werden würde.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Wir reden offensichtlich aneinander vorbei.
Aber ich verstehe immer noch nicht, warum Signale eingesetzt werden sollen. Wenn man mit ``idle_add`` eine emit Funktion aufruft, kann man doch gleich die Methode direkt aufrufen, die mit dem Signal aufgerufen werden würde.
Die Frage zielt eher in Richtung generelle Sinnhaftigkeit von Signalen, die Dir sicherlich klar ist. Womit auch die Frage beantwortet sein sollte. (Ich habe das auch schon mehrfach in diesem Forumthread angerissen.)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Letztlich lässt sich das alles ohne konkreten Code des Threads doch gar nicht sagen. Die ursprüngliche Idee ist es ja, serielle Geräte anzusteuern. Zumindest nach meiner Vorstellung muss aber Code, der ein Gerät steuert, nichts über die GUI wissen.

Und dann ist eben die Frage, wie man diesen allgemeinen, von der GUI unabhängigen Code jetzt in die GUI einbindet. Eine Möglichkeit wären eben einfach Callbacks (in oder ohne Verbindung mit einem Thread, mit oder ohne dass der Aufrufer den Callback indirekt durch `gobject.idle_add()` aufruft (man kann ja auch einfach `gobject.idle_add` als Callback übergeben)), eine andere Möglichkeit wären sicherlich aber auch Signale. Signale sind in GUIs nichts ungewöhnliches und man könnte damit dann solche Sachen machen, dass man ein Logging-Fenster hat, das einfach nur die rohen Daten anzeigt, die empfangen werden und dass man ein Fenster hat, in dem diese Daten dann grafisch aufbereitet werden. Mit Signalen kann man nun eben einfach das gleiche Objekt mehrfach unabhängig voneinander verwenden, was IMHO recht elegant ist, zumal einem die dazu benötigten Dinge zur Verfügung stehen, ohne dass man sie selber implementieren müsste.

Wie genau man es macht, hängt jetzt halt davon ab, was genau man vorhat und der persönliche Geschmack spielt sicherlich auch eine Rolle. Wichtig ist, und darum ging es mir in diesem Thread, dass man mit Threads eben aufpassen muss.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Antworten