Python threading blockiert UI

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
patrice079
User
Beiträge: 13
Registriert: Sonntag 15. April 2012, 13:22

Hallo,

ich habe eine kurze Frage und vermute das ich nur etwas nicht ganz verstehe. Ich möchte in einer PyQt4 GUI einen Prozess ausführen der etwas Zeit beansprucht und deshalb parallel eine Progressbar updaten. Was ich versuche, aber nicht funktioniert, ist das Folgende wenn der User auf einen Button klickt:

Code: Alles auswählen

from threading import Thread
t = Thread(target=self.project.parseConfig, args=(fname,))
t.start()
self.startProgressbar()
t.join()
self.stopProgressbar()
startProgressbar ruft dann regelmäßig mittels

Code: Alles auswählen

QtCore.QTimer.singleShot(10, self.updateProgressbar)
updateProgressbar auf. Allerdings friert die UI trotzdem ein. Kann mir jemand meinen Denkfehler aufzeigen, ich hätte erwartet das nach t.start() die Funktion project.parseConfig in einem eigenen Thread läuft und mir mein Mainthread in startProgressbar bis zum t.join() weiterläuft?

vielen Dank vorab für jede Hilfe.

Grüße
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@patrice079: was passiert denn in startProgressbar? Wie rufst Du singleShot auf? Per while-Schleife? Blockierst Du damit in startProgressbar die GUI?
patrice079
User
Beiträge: 13
Registriert: Sonntag 15. April 2012, 13:22

Nein, ich rufe die Funktion mit singleshot einfach immer wieder auf:

Code: Alles auswählen

def startProgressbar(self):
       self.__pbar.show()
       self.updateProgressbar()


def updateProgressbar(self):
            QtCore.QTimer.singleShot(10, self.updateProgresssbar)
            # here update progressbar stuff
BlackJack

@patrice079: Warum denn dann `singleShot()` wenn das dazu benutzt wird wiederholte Aufrufe zu machen?

Ich tippe mal darauf dass das `t.join()` Dir die GUI blockiert.
patrice079
User
Beiträge: 13
Registriert: Sonntag 15. April 2012, 13:22

Das tut es auch, aber wie kann ich das elegant lösen? Ich muss auf das Ergebnis von self.project.parseConfig warten um weitermachen zu können aber gleichzeitig eben die Progressbar updaten?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

patrice079 hat geschrieben:Ich muss auf das Ergebnis von self.project.parseConfig warten um weitermachen zu können aber gleichzeitig eben die Progressbar updaten?
Vorschlag: Füge den Paramter `tell_progress` zur Signatur von `parseConfig()` hinzu. Wenn dieser einen anderen Wert als `None` hat, dann wird er als Callable angesehen und innerhalb von `parseConfig()` nach jedem Zwischenschritt aufgerufen. Für diesen Parameter kannst du beim Aufruf von `parseConfig()` dann die entsprechende Methode aus deiner Progressbar übergeben.

Dadurch, dass `parseConfig()` ganz abstrakt nur ein Callable erwartet, erreichst du eine einigermaßen lose Kopplung, was zur Wiederwendbarkeit beiträgt - also falls man mal eine andere Art von Fortschrittsanzeige verwenden möchte.

Für eine richtige Fortschrittsanzeige wäre es natürlich noch sinnvoll zu wissen, an welchem Punkt des Gesamtprozesses man gerade steht. Dementprechend könnte man die Funktionalität noch etwas ausgeklügelter gestalten und z.B. vorab die Gesamtzahl an Zwischenschritten mitteilen, sofern dies ohne großen Aufwand möglich ist. `tell_progress` wäre dann entsprechend ein Callable mit (mindestens) einem Parameter, wo die Anzahl der bereits vollzogenen Zwischenschritte mitgeteilt wird. Oder man macht dies implizit (ohne zusätzliche Parameter), indem man die Anzahl der Aufrufe von `tell_progress` mitzählt und diese ins Verhältnis zur vorab angegebenen Gesamtzahl setzt. Es gibt hier, wie man sieht, verschiedene Möglichkeiten.

Was ich persönlich *nicht* machen würde, ist stumpf nach einem bestimmten Intervall die Progressbar "anzustupsen". Ich würde einen Fortschritt nur dann angeben, wenn er auch tatsächlich umgesetzt wurde, d.h. wenn der Gesamtprozess wirklich ein Stück weiter gekommen ist.
BlackJack

@patrice079: Einen Thread zu starten, nur um dann blockierend auf ihn zu warten ist unsinnig. Du könntest beispielsweise von `QThread` erben und Dich über ein Signal darüber informieren lassen wenn die Arbeit erledigt ist. Oder Du schaust Dir mal den Inhalt vom QtConcurrent-Namensraum an, mit `QFuture` und `QFutureWatcher`.
patrice079
User
Beiträge: 13
Registriert: Sonntag 15. April 2012, 13:22

Danke Euch allen, ich habe es über einen Signal gelöst das am Ende des Threads gefeuert wird.
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Wenn du einen QThread verwendest: der feuert eh am Ende ein Signal.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wenn nur am Ende ein Signal gefeuert wird, dann frage ich mich nach dem Sinn der Fortschrittsanzeige. Geht es vielleicht nur darum, ganz allgemein anzuzeigen, dass etwas passiert und aufzuhören, wenn der Prozess beendet ist?
Antworten