GTK und Threads / Threading, Gobject !?

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

GTK und Threads / Threading, Gobject !?

Beitragvon whaeva » Freitag 6. März 2009, 18:34

Liebe Leute,
ich versuche schon den halben Tag ein aktuelles Tutorial zu finden, das beispielhaft ein Programm mit GTK-GUI und Threads o.Ä. erklärt.
Und zwar aus dem Grund, dass ich schnellstmöglich was am Laufen haben möchte, ohne mich tiefgründig einzuarbeiten.
Die Tutorials, die ich gefunden habe, sind zum einen etwas veraltet, zum anderen werden die Zusammenhänge nicht weiter erklärt.

Der erste Reinfall ist ja schonmal, dass man Threads und GTK nicht ohne weiteres mischen kann, weil GTK natürlich selbst Threads benutzt.
(Anscheinend ist das Modul threads sowieso ein ausaufmodell und man sollte lieber mit threading eigene thread-klassen erstellen.)

Dann habe ich irgendwo gelesen, dass man sowieso besser Methoden aus gobjects benutzen sollte.

Ein Versuch mit gtk.gdk.threads_init() und gtk.threads_enter() bzw. gtk.threads_leave() führte ins Leere

Hier ist ein Snippet, wo es vermutlich richtig gemacht wird:
http://www.python-forum.de/topic-2711.html

[edit: auch schon veraltet:
GtkDeprecationWarning: gtk.threads_enter is deprecated, use gtk.gdk.threads_enter instead]


Hier ein ausführlicher Thread zu, Thema gobject
http://www.python-forum.de/topic-17662, ... ht=gobject

Hier noch ein paar Links:
http://www.pygtk.org/docs/pygtk/
http://docs.python.org/library/thread.html besser:
http://docs.python.org/library/threading.html
http://www.pygtk.org/docs/pygobject/

Hat zufällig jemand Lust die Allgemeinheit mit einem kleinen Tutorial zu bereichern?
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Freitag 6. März 2009, 18:59

Die Frage ist: was willst du denn machen? Ich brauche in GTK-Applikationen quasi nie Threads, dafür reicht oftmals schon ``gobject.idle/timeout_add``.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Freitag 6. März 2009, 19:43

Hi,

Ziel ist ein Programm, das mehrere physikalische Größen über serielle Verbindungen steuert und ausliest, grafisch darstellt (evtl. matplotlib ?), die Daten bewertet und sowohl roh als auch ein Bericht evtl. als PDF auf Festplatte schreibt. (http://www.python-forum.de/topic-18055.html)

Die Kommunikation könnte man wahrscheinlich mit entsprechender Wartezeit in einer Schleife auch nacheinander machen.

Ich hatte allerdings angenommen, dass mit einem Thread vielleicht die Auslesefrequenz eines Gerätes maximiert werden kann, weil nicht auf anderes gewartet werden muss.
Es sollte nämlich eine Abbruchbedingung von einem Gerät so schnell wie möglich erfasst und daraufhin ein Befehl an ein anderes Gerät gesendet werden.

Bislang habe ich Klassen für die verschiedenen Geräte geschrieben, die die serielle Verbindung herstellen, und den Datenaustausch bzw. die Umwandlung von Werten durchführen. Knackpunkt ist da eigentlich nur die Prüfung auf Fehler (Nicht eingeschaltet oder falsch eingestellt -> timout der Verbindung -> Rückgabewert_Fehler = True), daher auch die Frage im anderen Thread. Die Behandlung dieses Problems ist wahrscheinlich mit "try: except:" eleganter als mit "if gerät.read()[3] dann nochmal"..

Wenn man auf Basis dieser Klassen ein Thread Objekt erstellen könnte, mit denen nebem dem Hauptthread auch die anderen Threads kommunizieren könnten, wär' das doch eine "gute" Lösung?

Jetzt möchte ich allerdings klein anfangen und erstmal eine einzelne Verbindung als Thread auslesen und anzeigen, mit dem genannten Codesnippet bin ich da mittlerweile auf einem funktionierenden Weg.

Wahrscheinlich sollte ich jedoch auf das Modul Threading umsteigen und die "Funktion" als Klasse schreiben.

Also, falls du mein Vorhaben durchschaust, würde ich mich über Tipps freuen!
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 12:51

Leider stoße ich selbst bei dem Minimalprogramm an meine Grenzen.
Es startet einen Thread und läuft erwartungsgemäß, allerdings wird der Thread nicht beendet und auch sonst weiß ich nicht, ob das so im Sinne des Erfinders ist..
Hilfe!?

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import threading
import Queue
import types
import time
import gtk            # GTK+ 2


class mySerialDevice(object):
   
   def __init__(self, port):
      self.port = port
   
   def connect(self):
      pass
      #~ try:
         #~ self.serial = serial.Serial(   port=self.port,
                                 #~ baudrate=9600,
                                 #~ bytesize=serial.EIGHTBITS,
                                 #~ parity=serial.PARITY_NONE,
                                 #~ stopbits=serial.STOPBITS_ONE,
                                 #~ timeout = 2
                              #~ )
      #~ except serial.SerialException:
         #~ exit("Cannot open serial port")

   def disconnect(self):
      pass
      #~ self.serial.close()


class myThread(threading.Thread):

   def __init__(self, label):
      """
      """
      threading.Thread.__init__(self)
      self.label = label
      self.counter = 0
      self.dev = mySerialDevice("/dev/ttyS0")
      self.dev.connect()


   def run(self):
      """
      """
      
      while True:
         self.counter += 1
         time.sleep(1)
         gtk.gdk.threads_enter()
         self.label.set_text("%d" % self.counter)
         gtk.gdk.threads_leave()
         
   def stop(self):
      self.dev.disconnect()
      self.kill() # WIE?
      


class myGUI:
   """
   """

   def __init__(self):
      builder = gtk.Builder()
      builder.add_from_file("test.xml")
      builder.connect_signals(self)
      
      self.window = builder.get_object("window")
      self.label = builder.get_object("label")
      
      self.test = myThread(self.label)
      self.test.start()
      
      self.counter = 0


   def run(self):
      pass


   def main(self):
      gtk.main()


   def on_window_destroy(self, widget, data=None):
      self.test.stop()
      gtk.main_quit()


   def on_button_clicked(self, widget, data=None):
      self.label.set_text("button_clicked")





if __name__ == "__main__":
   gtk.gdk.threads_init()

   app = myGUI()
   gtk.gdk.threads_enter()
   app.main()
   gtk.gdk.threads_leave()


Hier das mit
[code=]gtk-builder-convert test.glade test.xml[/code]

für gkt.builder() konvertierte XML:

Code: Alles auswählen

<?xml version="1.0"?>
<!--Generated with glade3 3.4.5 on Sat Mar  7 12:31:49 2009 -->
<interface>
  <object class="GtkWindow" id="window">
    <property name="visible">True</property>
    <property name="title" translatable="yes">Window Title</property>
    <signal handler="on_window_destroy" name="destroy"/>
    <child>
      <object class="GtkVBox" id="vbox1">
        <property name="visible">True</property>
        <child>
          <object class="GtkLabel" id="label">
            <property name="visible">True</property>
            <property name="label" translatable="yes">label</property>
          </object>
        </child>
        <child>
          <object class="GtkButton" id="button">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="label" translatable="yes">button</property>
            <signal handler="on_button_clicked" name="clicked"/>
          </object>
          <packing>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 13:26

Nach einem sinnlosen Versuch mit threading.event liest man bei
http://docs.python.org/library/threading.html#id1
Once the thread’s activity is started, the thread is considered ‘alive’. It stops being alive when its run() method terminates – either normally, or by raising an unhandled exception. The is_alive() method tests whether the thread is alive.
Die endlose Whileschleife in myThread.run() braucht also eine Abbruchbedingung, damit der Thread beendet werden kann.

Trotzdem würde mich interessieren, ob das so der richtige Weg ist.
jerch
User
Beiträge: 1622
Registriert: Mittwoch 4. März 2009, 14:19

Beitragvon jerch » Samstag 7. März 2009, 13:57

Ein Code Bsp. findest Du unter http://www.python-forum.de/topic-18028.html 2. Post. Hier siehst Du, wie Du die Abbruchbedingung in Dein while packst. Da Du mit einem Klassenansatz fährst, müßtest Du das ein wenig umstricken (Event-Initialisierung in Konstruktor, set() anstelle Deines kills, while wie gehabt usw.)
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 13:59

Schon ergibt sich die nächste Frage:

Im Beispiel bekommt myThread ein Handle zum Zugriff auf das Widget label.
Im echten Programm sind allerdings noch weitere, abhängige Labels zu ändern, myThread soll auf Anweisung vom GUI auch noch andere Funktionen ausführen, usw.

Wie entscheide ich, was in den Thread, und was in die GUI gehört?
Lässt sich der Datenaustausch zwischen Thread und GUI irgendwie allgemeiner durchführen?
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 14:04

@ jerch:
Danke, den Schleifenabbruch habe ich bislang mit einer Variable gelöst, aber mit signal geht's natürlich auch - was ist der Vorteil vom signal (abgesehen von der wait funktion)?
jerch
User
Beiträge: 1622
Registriert: Mittwoch 4. März 2009, 14:19

Beitragvon jerch » Samstag 7. März 2009, 15:04

Wie entscheide ich, was in den Thread, und was in die GUI gehört?
Lässt sich der Datenaustausch zwischen Thread und GUI irgendwie allgemeiner durchführen?


Hm, das läßt sich nicht so einfach beantworten. Für Multithreading-Anwendungen gehört alles gui-spezifische definitiv in die "Gui-Klasse", sollte also gui-nah behandelt werden. Die Komunikation würde ich dann über signals realisieren, so können mehrere konkurrierende Threads ohne lästige Locks weiterarbeiten (was halt passiert, wenn ein zweiter Thread versucht, einen Mutex zu erhalten während ein anderer ihn noch hält). Die Signale würden dann einfach von event-handler der Gui nacheinander abgearbeitet.
Den Weg, den Du im Moment gehts, holt sich den Mutex (threads_enter()), beschreibt eine "globale" Variable (Dein Label) und gibt den Mutex wieder frei (threads_leave()). Das ist vollkommen legitim, könnte aber bei mehreren Threads halt zu den beschrieben Locks führen (die Threads müßten dann unnötig angehalten werden). Für Deine Anwendung würde das meiner Meinung nach in Ordnung gehen, da Du myThread nur einmal anwirfst. Trotzdem würde ich eher den Signalweg bevorzugen. Dann hättest Du auch eine bessere Trennung von Gui-stuff und Thread-Logik.
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 17:20

"Hi" und danke für deine Unterstützung.
Ich bin leider nicht sicher, dass ich dich verstanden habe:

myThread macht sein Ding, bekommt ein Ergebnis, schreibt das in eine echte, globale Variable und setzt ein Signal.

myGUI (=gtk) wartet auf dieses Signal (wie?) und aktualisiert dann "label" mit dem Datum aus der globalen Variable.

So? Dann müsste ich ja GTK noch beibringen, auf das Signal zu reagieren.

In irgendeiner Newsgroup habe ich eben auch etwas davon gelesen, ein globales Dictionary anzulegen, dass dann für den Datenaustausch dient. Ist das der "beste" Weg? Geht das nicht in Richtung Queue?

http://docs.python.org/library/queue.html
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 17:40

Habe ein sehr gutes Tutorial zum Thema Threading und Queues gefunden:
http://www.ibm.com/developerworks/aix/l ... index.html

Queues sind wohl nicht Lösung für mein Problem.
Ich suche wohl eher den Zusammenhang zwischen einem "eigenen" Event und dem builder.connect_signals(self).
jerch
User
Beiträge: 1622
Registriert: Mittwoch 4. März 2009, 14:19

Beitragvon jerch » Samstag 7. März 2009, 18:21

Hui, ich sagst mal so, viele Wege führen nach Rom.
Ich bin leider nicht sicher, dass ich dich verstanden habe:

myThread macht sein Ding, bekommt ein Ergebnis, schreibt das in eine echte, globale Variable und setzt ein Signal.

Naja nicht so ganz, ich meinte eher -

Entweger:
Globaler Zugriff, wie Du es mit dem Label schon machst, nur sinnvoll für eins/zwei direkte Gui-"Eingriffe", sonst hast Du zuviel Gui-Code im Thread, wie selbst schon bemerkt hast. Für wenige Manipulationen fänd ich das noch azeptabel, andere würden Dir sofort schlechten Programmierstil vorwerfen und mit zig Programmierparadigmen wedeln. ;) Eine gute Trennung zw. Thread- und Guilogik hättest Du so halt nicht, was definitiv auf Kosten der Wartbarkeit geht.

Oder:
Du nutzt Signale. Das tolle Gui-Libraries wie Gtk, Qt ist doch, das das Eventhandling Dir quasi geschenkt wird und mit Signal/Slot-Konzepten die Kommunikation zw. verschiedenen Objekten sehr einfach wird. Unten findest Du ein grobes Schema für PyGTK, wie man das in Deinem Falle nutzen könnte.

In irgendeiner Newsgroup habe ich eben auch etwas davon gelesen, ein globales Dictionary anzulegen, dass dann für den Datenaustausch dient. Ist das der "beste" Weg? Geht das nicht in Richtung Queue?

Auch hiermit würdest Du zum Ziel kommen, müßtest Dich aber selbst um das Eventdispatching kümmern. Da Deine Grundbibliotheken aber schon alles mitbringen, ist das viel zu umständlich. (siehe oben Oder:)

Editiere Deinen Code doch mal wie folgt:

Code: Alles auswählen

import gobject
...
class myThread(threading.Thread, gobject.GObject):
   # signale deklarieren
   __gsignals__ =  {"update":(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, [gobject.TYPE_INT])}
   def __init__(self, label):
      ...
      gobject.GObject.__init__(self)

   def run(self):
      ...
         # in der while-Schleife anstelle der
         # gtk.gdk.threads_enter()
         # self.label.set_text("%d" % self.counter)
         # gtk.gdk.threads_leave()
         self.emit("update", self.counter) # signal "update" absetzen

class myGUI:
   def __init__(self):
      ...
      self.test = myThread(self.label)
      # signal test.update mit updateLabel verbinden
      self.test.connect("update", self.updateLabel)
      self.test.start()
...
   def updateLabel(self, widget, num):
      self.label.set_text("%d" % num)

Mit der zusätzlichen Ableitung von GObject erhält MyThread einige Basisfunktionalitäten der Gtk-Objekte, unter anderen kann MyThread jetzt Signale absetzen, die Deine Gui-Klasse versteht. Dadurch kannst Du mit MyThread wie mit jedem anderen Gtk-Objekt (genauer GObject) via Signal/Slot reden. Großer Vorteil, die Signale sind Events die normal via connect() verbunden und vom eingebauten Eventhandling automatisch abgearbeitet werden.

Grüße, Jerch
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 18:52

Perfekt, danke für die Erläuterung. Ich probiere das jetzt mal aus.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Beitragvon Trundle » Samstag 7. März 2009, 19:08

Vorsicht: Man darf grundsätzlich nicht mit mehreren Threads auf die GUI zugreifen. Die Signal-Handler werden jedoch im gleichen Thread aufgerufen, in dem das Signal ausgelöst wurde. Das bedeutet also, dass Signal-Handler des "update"-Signals des ``myThread``-Objekts nicht direkt auf die GUI zugreifen dürfen.

Außerdem ist noch anzumerken, dass `gtk.gdk.threads_enter()`/`gtk.gdk.threads_leaver()` unter Windows nicht zuverlässig funktioniert, da man nur von einem Thread aus (und nicht einfach nur nicht mit mehreren gleichzeitig) auf GDK-Funktionen zugreifen sollte, also sollte man lieber `gobject.idle_add` verwenden (siehe auch "Functions for using GDK in multi-threaded programs", Abschnitt "Description").
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
whaeva
User
Beiträge: 66
Registriert: Mittwoch 25. Februar 2009, 15:30

Beitragvon whaeva » Samstag 7. März 2009, 20:19

Also das Programm läuft jetzt schon ein bisschen mehr :-)
Wenn es unter Windows nicht liefe, ist das für mich nicht schlimm.

Zu deinem 2. Satz: Ich bin der Meinung, dass dem nicht so ist. Das Signal/Event wird in "MyThread" emitted, und in myGUI ist der Handler. ?

Unabhängig davon habe ich noch eine Frage:
In "myGUI" ist "label" durch "updateLabel" aktualisiert worden.
Den übermittelten Wert kann ich in updateLabel ja auch noch für meine anderen Zwecke Nutzen. Gibt es theoretisch auch die Möglichkeit, durch self.label.set_markup() ein Event auszulösen?

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder