Signal connect im WorkerThread (also mal andersrum)

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
derhendrik
User
Beiträge: 17
Registriert: Donnerstag 15. Januar 2015, 16:39

Hallo Python-Freunde,

nachdem mir bei meinem letzten Post schon so fachmännisch geholfen wurde, versuche ich es nochmal.
Bitte entschuldigt diesen komischen Betreff, wusste nicht, wie ich es sonst am besten beschreiben kann.

Meine Ausgangssituation ist Folgende:

Ich habe einen WorkerThread, welcher im Hintergrund zyklisch über einen QTimer eine Aufgabe erfüllt. Ist diese beendet, wird ein Signal emittiert.
In meinem MainWindow "connecte" ich zu diesem Signal. Das funktioniert wunderbar.
Hier mal der Code, auf das Wesentliche reduziert.

Code: Alles auswählen

import sys
from PyQt4 import QtCore,QtGui

class WorkerThread(QtCore.QThread):
    def __init__(self):
        super(WorkerThread,self).__init__()

    def run(self):
        self.emit(QtCore.SIGNAL("aus_dem_worker"))       


class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()        
        self.testthread = WorkerThread()
        self.connect(self.testthread, QtCore.SIGNAL("aus_dem_worker"), self.schreibe)
        self.testthread.start()        

    def schreibe(self):
        print "Hallo Welt, gestartet von dem testthread"
        

def main(argv):    
    app = QtGui.QApplication(argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())        
        
if __name__ == '__main__':
    main(sys.argv)
Jetzt ist mein Plan aber, dass ich nicht nur von dem WorkerThread ein Signal emittiere, sondern diesen Mechanismus auch in umgekehrter Richtung verwende:

In meinem MainWindow soll beispielsweise ein Knopf gedrückt werden, woraufhin eine Funktion in dem WorkerThread aufgerufen wird. Analog zum vorherigen Beispiel will ich den threadsicheren Signal/Slot-Mechanismus verwenden. Mein ursprünglicher Ansatz war entsprechend Folgender:

Code: Alles auswählen

import sys
from PyQt4 import QtCore,QtGui

class WorkerThread(QtCore.QThread):
    def __init__(self):
        super(WorkerThread,self).__init__()
        self.connect(????????,QtCore.SIGNAL("aus_dem_mainwindow"),self.schreibe)

    def run(self):
        # Hier steht im urspruenglichen Code die zyklische Berechnung von Werten drin
        return

    def schreibe(self):
        print "Hallo Welt, gestartet durch MainWindow"               


class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()        
        self.testthread = WorkerThread()
        self.testthread.start()
        button = QtGui.QPushButton("Starte Funktion 'schreibe' aus WorkerThread")
        button.clicked.connect(self.starte_funktion_aus_workerthread)
        self.setCentralWidget(button)

    def starte_funktion_aus_workerthread(self):
        self.emit(QtCore.SIGNAL("aus_dem_mainwindow"))
        

def main(argv):    
    app = QtGui.QApplication(argv)
    mainwindow = MainWindow()
    mainwindow.show()
    sys.exit(app.exec_())        
        
if __name__ == '__main__':
    main(sys.argv)

Leider komme ich hier nicht weiter (siehe "??????" in Zeile 7). Im Internet habe ich einige Forenbeiträge gefunden, die mich vermuten lasse, dass ich etwas Wesentliches (Grundlegend Programmierung, nicht PyQt-spezifisch) nicht verstanden habe.
Wie gehe ich dieses Problem am besten an? Der erste Parameter der connect-Funktion muss anscheinend vom Typ QObject sein, falls das weiterhilft. Ich glaube, jemand hat ein ähnliches Problem gelöst, indem er einen eigenen "Emitter", welcher von QObject erbt, erstellt hat.

Oder ist etwas grundlegend falsch mit der Logik, die ich hier versuche umzusetzen? (also ja eigentliches Problem: Funktionen aus einem laufenden Thread abrufen)

Vielleicht ein paar wichtige Hintergrundinformationen: Leider muss sowohl das zyklische "Lesen" als auch das "Schreiben" im selben Thread erfolgen, da hier über die serielle Schnittstelle eine Verbindung zum Mikrocontroller aufgebaut wird.

Bin über jede Hilfe dankbar!
derhendrik
User
Beiträge: 17
Registriert: Donnerstag 15. Januar 2015, 16:39

Okay, habe für diesen Forenpost über eine halbe Stunde gebraucht, was mich gezwungen hat, nochmal genau darüber nachzudenken.

Jetzt klappt es sogar ziemlich einfach:


In meinem "MainWindow" mache ich jetzt einfach so (zweites Beispiel):

button.clicked.connect(self.testthread.schreibe)

Wie Banane von mir :D

Vielleicht können wir ja trotzdem diskutieren, wie man jetzt noch Parameter übergibt? Evtl. Lambda-Funktion?
BlackJack

@derhendrik: Da wo die Fragezeichen stehen müsste das Quellobjekt, also das `MainWindow`-Exemplar angegeben werden. Das müsste man dann beispielsweise dem `WorkerThread`-Exemplar beim erstellen als Argument übergeben.

Anstelle von `QtCore.SIGNAL()` und einer Zeichenkette als Signalnamen und der `connect()`-Methode auf `QObject` würde ich ja eher ein `pyqtSignal` auf der Klasse definieren und dessen `connect()`- und `emit()`-Methoden verwenden.
derhendrik
User
Beiträge: 17
Registriert: Donnerstag 15. Januar 2015, 16:39

@BlackJack:

Danke dafür! Ja, das habe ich mir auch gedacht, aber vergessen, dieses als Argument zu übergeben.
Jetzt funktioniert es auch, indem ich einfach die Funktion des Threads aufrufe (anscheinend ohne den Signal/Slot-Mechanismus). Ich nehme aber mal an, dass da einiges schiefgehen kann und werde meinen Code an deinen Vorschlag anpassen!

Schönes Wochenende!

Hendrik
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@derhendrik:
In die andere Richtung funktioniert der Signal/Slot-Mechanismus nur, wenn im Subthread ein Eventloop läuft, Du quasi auch dort ein .exec_() analog zum Mainthread hast. Der direkte Aufruf der Methode bzw von `button.clicked.connect(self.testthread.schreibe)` führt nicht zum Threadkontextwechsel.
Antworten