Signale aus mehreren Threads verarbeiten

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Hallo,

im Prinzip geht es darum, in einem mit Qt-Designer erstellten Fenster, eine Fortschrittsanzeige (QProgressBar) zu steuern, deren Wert jedoch von Berechnungen, die in einem separaten Thread durchgeführt werden, abhängt.

Abstrahiert sieht das dann ungefähr so aus:

Code: Alles auswählen

from PyQt4 import QtCore
import threading

class A(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.sigobj = QtCore.QObject()

    def run(self):
        self.sigobj.emit(QtCore.SIGNAL('doIt'))
        
class B():
    def __init__(self):
        self.handleA = A()
        QtCore.QObject.connect(self.handleA.sigobj, QtCore.SIGNAL("doIt"), self.xyz)        
        
    def xyz(self):
        print "XYZ!"

handleB = B()
handleB.handleA.start()
Wieso führt hier die Emission des Signals durch Zeile 10 nicht dazu, dass Zeile 17 ausgeführt wird?

Meine Vermutung ist, dass die Zeile

Code: Alles auswählen

QtCore.QObject.connect(self.handleA.sigobj, QtCore.SIGNAL("doIt"), self.xyz)
eigentlich Teil der Klasse A sein müsste, da der einfache Aufruf:

Code: Alles auswählen

handleB.handleA.sigobj.emit(QtCore.SIGNAL('doIt'))
korrekterweise "XYZ!" ausgibt.

Wie bekomme ich hier die Funktionalität, die ich gerne hätte, nämlich, dass die Klasse A ein Signal triggert, dessen Slot in der Klasse B liegt? In B kann ich die Signal-Slot-Zuweisung ja nicht durchführen, da B keine Instanz von A hat (oder so ähnlich... ich denke ihr wisst, was ich meine).

Was ich auch nicht verstehe, ist dass die Aufrufe:

Code: Alles auswählen

print self.sigobj
(aufgerufen in __init__(self) von Klasse A) und

Code: Alles auswählen

handleB.handleA.sigobj.emit(QtCore.SIGNAL('doIt'))
(aufgerufen von außen)

die gleiche Adresse liefern, die emit-Methode aber trotzdem nur aus Klasse B funktioniert.

Vielen Dank im Voraus!
BlackJack

@jbb: Du musst halt noch die Qt-Hauptschleife aufrufen, damit die Signal- und Ereignisverarbeitung von Qt abgearbeitet werden kann. Also mindestens noch ein `QtCore.QCoreApplication()`-Objekt erstellen und dessen `exec_()`-Methode aufrufen. Dann funktioniert das bei mir.

Allerdings würde ich trotzdem den von der Dokumentation dringend empfohlenen Weg nehmen und neue Signale mit `pyqtSignal()` auf von `QObject` abgeleiteten Klassen definieren. An der Stelle würde sich auch `QThread` statt `threading.Thread` anbieten, damit wäre `A` dann auch selbst automatisch schon ein `QObject`-Abkömmling.
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Hallo BlackJack,

vielen Dank für die schnelle Rückmeldung. Ich habe nun beides mal versucht, habe es aber leider nicht wirklich hinbekommen. Könntest Du mir evtl. die relevanten Code-Schnipsel kurz posten?

Besten Dank,
jbb
BlackJack

Also bei mir reichte es `sys` zu importieren und Deine beiden letzten Zeilen durch zwei weitere zu ergänzen:

Code: Alles auswählen

app = QtCore.QCoreApplication(sys.argv)
handleB = B()
handleB.handleA.start()
sys.exit(app.exec_())
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Tatsache! Nicht, dass ich verstehen wirklich würde, was da abgeht, aber es funktioniert so. Danke dafür! Das Problem ist nun aber, dass in meinem richtigen, d.h. nicht abstrahierten Code schon folgendes steht:

Code: Alles auswählen

app = QtGui.QApplication(sys.argv)
window = MyWindow()
sys.exit(app.exec_())
Was im abstrahierten Beispiel also die QCoreApplication macht, müsste hier von QApplication übernommen werden, sehe ich das richtig? Obwohl ich es nicht weiß, würde ich gefühlsmäßig sagen, dass man nicht beides, d.h. QCoreApplication() und QApplication() haben sollte, ist das richtig? Kann ich die QApplication "überreden", das zu machen, was im Beispiel die QtCoreApplication macht, d.h. die Signale korrekt zu verarbeiten?

Danke und Gruß,
jbb
BlackJack

@jbb: `QApplication` ist eine Unterklasse von `QCoreApplication` und damit sollte Dein Programm eigentlich funktionieren. Also muss dort irgendetwas anders sein was beim Vereinfachen weggefallen ist.
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Danke BlackJack! Du hast recht, wenn ich nur die relevanten Zeilen aus dem Originalen Quellcode nehme und wie im abstrahierten Beispiel anordne, dann funktioniert es, wie es sollte. Jetzt muss ich nur noch herausfinden, an welcher Stelle es stattdessen hakt...

Grüße,
jbb
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Gestern Abend habe ich nun das Problem in meinem Code lokalisiert: Die Methode, welche das Signal triggert wird nicht durch das eigentliche Objekt der Klasse, sondern von außerhalb aufgerufen. Da ich hier eine Bibliothek verwende, bin ich etwas ratlos, wie sich die gewünschte Funktionalität realisieren lässt. Hier ein Minimalbeispiel:

Code: Alles auswählen

from PyQt4 import QtCore
import threading, sys, mylibrary
 
class A(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)
 
    def run(self):
        mylibrary.startroutine()

    def triggeredByMylibrary(self):
        self.emit.(QtCore.SIGNAL('doIt'))
       
class B(QtGui.QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        uic.loadUi('myGui.ui', self)
        self.handleA = A()
        QtCore.QObject.connect(self.handleA, QtCore.SIGNAL("doIt"), self.xyz)        
       
    def xyz(self):
        print "XYZ!"

app = QtGui.QApplication(sys.argv)
window = B()
window.handleA.start()
sys.exit(app.exec_())
Dabei ist mylibrary die Bibliothek, die ich brauche. Diese ruft mir wiederum während der Routine startroutine() die Methode triggeredByMylibrary() auf. Dadurch wird vermutlich der Sender des Signals nicht als A() identifiziert, weswegen der Slot nicht ausgeführt wird. Gibt es hier eine mehr oder minder elegante Lösung, d.h. ohne Benutzung einer globalen Variablen und ohne den Code von mylibrary zu modifizieren?

Danke schonmal.
jbb
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Wann und vor allem wie wird denn

Code: Alles auswählen

triggeredByMylibrary()
von dieser Library aufgerufen?

Deine QApplication startet einen Thread, der wiederum irgendeine Methode einer Library aufruft, welche dann wiederum eine Methode des gleichen Threads aufrufen soll, die dann letztendlich ein Signal an den UI-Thread sendet.
Wozu machst du das bzw. was genau soll das werden?

Was passiert, wenn der Thread schon nicht mehr existent ist, deine Library aber versucht auf das Objekt zuzugreifen?

Der Sender ist in jedem Fall A, da das Signal vom QThread abgesetzt wird, nicht von deiner Library.
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Die Library enthält einen Parser, der binäre Daten einliest. Nach jeder gelesenen Zeile wird triggeredByMylibrary() ausgeführt, worin ich die gelesenen Daten weiter verarbeite. Darüber hinaus würde ich mit dieser Methode gerne einen Fortschrittsbalken in meinem GUI steuern.

Wie die Bibliothek intern gecoded ist, weiß ich leider nicht. Wenn man jedoch die entsprechende Methode in der Klasse A deklariert, dann läuft der da auch durch, das habe ich ausprobiert. Beim Erzeugen des Library-Objekts wird lediglich eine Handle auf die geöffnete Datei übergeben, soweit ich das überblicke. Ich hoffe das sind alle Infos, die Ihr braucht.

Danke!
jbb
BlackJack

@jbb: Woher weiss denn `mylibrary.startroutine()` von dem `A`-Exemplar?
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

BlackJack hat geschrieben:@jbb: Woher weiss denn `mylibrary.startroutine()` von dem `A`-Exemplar?
@ jbb genau das meinte ich mit: "Wann und vor allem wie wird denn..."
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Sorry für das Missverständnis und nochmals Danke für eure Geduld! Ich hab mich etwas durch den Code der Library gewühlt und habe folgendes herausgefunden:
Was ich vorher nicht wirklich erkannt habe, ist, dass ich an einer Stelle eine Handle auf das Objekt der Klasse A übergebe. In der Bibliothek wird nun mit den Methoden hasattr(...), setattr(...) und getattr() eine Verknüpfung zu Methoden der Klasse A gemacht, welche einer bestimmten Namensgebung folgen. Somit weiß `mylibrary.startroutine()` von dem 'A'-Exemplar. Nun verstehe ich aber trotzdem nicht, wieso es für die Signale offensichtlich einen Unterschied macht, von wo aus das Signal emmitiert wird.
BlackJack

@jbb: Ohne das Problem nachvollziehen zu können fällt mir auch nichts mehr dazu ein. Kannst Du ein kleines lauffähiges Beispiel mit dem Problem erstellen in dem Du `mylibrary` bis zum nötigen Grad nach baust?
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

@BlackJack: Trotzdem Danke für die Bemühungen. Zuerst wollte ich in meinem letzten Post noch Code hinzufügen, aber die Bibliothek ist für meine doch sehr überschaubaren Programmierkenntnisse zu verschachtelt aufgebaut und es gibt zu viele Konstruktionen, die ich überhaupt nicht verstehe, als dass ich das nachmachen könnte. Vielleicht bekomme ich hier am Montag jemanden an den Monitor, der sich das anschauen kann. Falls ich noch was rausfinde, werde ich mich melden.

Grüße und schönes Wochenende!
jbb
jbb
User
Beiträge: 9
Registriert: Donnerstag 30. Januar 2014, 11:52

Hallo nochmal,
das Problem ist gelöst! Ich hatte versehentlich zwei verschiedene Objekte der Klasse A erzeugt. Bei einem der beiden habe ich Signale und Slots verbunden, mit dem anderen Objekt die Signale dann geschickt. Im Nachhinein ein ziemlich doofer Fehler. Trotzdem nochmal besten Dank für euer Bemühen mir zu helfen!

Grüße,
jbb
Antworten