Seite 1 von 1

Update GUI mit Threads Problematik

Verfasst: Sonntag 2. Februar 2014, 00:14
von baer999
Hallo,

ich habe zurzeit ein kleines Problem beim Entwickeln einer GUI.

Sie soll nicht einfrieren, weshalb ich einen extra Thread erstellt habe. Dieser ist in einer eigenen Klassen und soll per EventHandling mit anderen Klassen kommunizieren können

Code: Alles auswählen

class myThread(multiprocessing.Process)

        def connect(self, event_name, callback):
                if self.callbacks is None:
                        self.callbacks = {}

                if event_name not in self.callbacks:
                        self.callbacks[event_name] = [callback]
                else:
                        self.callbacks[event_name].append(callback)

        def fire(self, event_name, data=None):
                if self.callbacks is not None and data in self.callbacks:
                        for callback in self.callbacks[data]:
                                callback(self)

        def run(self):
                       self.fire(self, "go")

,

Die Thread Klasse rufe ich aus dem der Klasse "dimm_gui" auf und binde das Ereignis "go" an die Funktion do_change_pbar => dort soll meine GTK ProgressBar mit einem neuen Wert aus einer Variable gefüllt werden (öffentliche Variable per sharedctypes):

Code: Alles auswählen

                myt = myThread()
                myt.connect("go", self.do_change_pbar)
                myt.start()

        def do_change_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
Mein Problem:
Das Update der Progressbar funktioniert nicht, warum kann ich mir nicht erklären, denn die Variable ist korrekt gefüllt und das "set_value" wird sauber ausgeführt. Aber die Oberfläche wird nicht entsprechend gäendert...

Kann sich das jemand erklären und kann mir einen Tipp geben?

Vielen Dank!

Re: Update GUI mit Threads Problematik

Verfasst: Sonntag 2. Februar 2014, 00:31
von Sirius3
@baer999: so wie Du die Callbacks implementiert hast, werden sie im zweiten Thread ausgeführt. Das funktioniert bei GUIs aber so nicht. Dann hättest Du im Thread ja gleich set_value direkt aufrufen können.
Einrücktiefe sind 4 Leerzeichen.
Warum initialisierst Du »self.callbacks« erst in der »connect«-Methode? Ein »defaultdict« würde Dir das Leben deutlich einfacher machen (aber noch nicht funktionsfähig, dazu brauchst Du Queues):

Code: Alles auswählen

class myThread(multiprocessing.Process)
    def __init__(self):
        self.callbacks = defaultdict(list)

    def connect(self, event_name, callback):
        self.callbacks[event_name].append(callback)
 
    def fire(self, event_name, *data):
        for callback in self.callbacks.get(event_name, ()):
            callback(self, *data)
 
    def run(self):
        self.fire(self, "go")

Re: Update GUI mit Threads Problematik

Verfasst: Sonntag 2. Februar 2014, 12:43
von BlackJack
@baer999: Ich würde an einer passenden Stelle statt eines direkten Aufrufs den Aufruf per `GObject.idle_add()` in die Gtk-Hauptschleife verlagern.

Re: Update GUI mit Threads Problematik

Verfasst: Sonntag 2. Februar 2014, 18:43
von baer999
@Sirius3: Was muss ich da ändern bzgl. Queues - ich verstehe, dass der Code durch defaultdict deutlich einfacher und lesbarer wird, aber ich bekomme das nicht hin (Fehler: "first argument must be callable")

Code: Alles auswählen

                self.keys = {}
                self.callbacks = defaultdict(self.keys)
@BlackJack: Wie setze ich das mit dem idle_add um? Ich habe hier mein Bsp. aber es wird irgendwie nicht aufgerufen?

Code: Alles auswählen

        def update_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
                print "update_pbar"

        def do_change_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
                gobject.idle_add(self.update_pbar, ())
Danke euch für die Hilfe, bin noch Pyhton Neuling, also nicht über die Unfähigkeit wundern ;-)

Re: Update GUI mit Threads Problematik

Verfasst: Sonntag 2. Februar 2014, 20:01
von BlackJack
@baer999: `defaultdict` will als Argument ein aufrufbares Objekt welches immer dann aufgerufen wird wenn man einen Schlüssel abfragt für den es noch keinen Wert gibt und diesen Wert erzeugt. Das sollte doch so in der Dokumentation stehen. Was willst Du mit dem `self.keys` erreichen? Das kommt doch in dem Beispiel von Sirius3 auch gar nicht vor.

Beim zweiten Beispiel dürfte Zeile 6 ein Problem sein wenn das nicht vom GUI-Thread abgearbeitet wird.

Re: Update GUI mit Threads Problematik

Verfasst: Montag 3. Februar 2014, 13:30
von baer999
Ja richtig, Zeile 6 ist Schwachsinn, aber mir geht es da um Zeile 7...

Wird per gobject.idle_add() nicht dem GUI Thread dieser Aufruf übergeben, damit auch tatsächlich die Progressbar einen Update erhält?

Klappt leider so bisher nicht...

Re: Update GUI mit Threads Problematik

Verfasst: Montag 3. Februar 2014, 14:17
von BlackJack
@baer999: Die Frage ist ob Zeile 7 überhaupt ausgeführt wird solange es davor die Zeile 6 gibt.

Re: Update GUI mit Threads Problematik

Verfasst: Montag 3. Februar 2014, 15:44
von diesch
Ein einfaches Beispiel, wie du Process, Queue und GLib.idle_add zusammenbringen kannst:

Code: Alles auswählen

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

from gi.repository import Gtk, GLib
import multiprocessing, time

def do_something(queue):
    for i in range(100):
        queue.put(i)
        time.sleep(0.1)

class MyApp(object):
    def __init__(self):
        win = Gtk.Window()
        self.pbar = Gtk.ProgressBar()
        win.add(self.pbar)
        
        q = multiprocessing.Queue()
        process = multiprocessing.Process(target=do_something, args=(q,))
        GLib.idle_add(lambda *args: self.update_pbar(q.get()) or True)
        
        win.show_all()
        process.start()

    def update_pbar(self, i):
        f = i/100
        self.pbar.set_fraction(f)

app = MyApp()
Gtk.main()