Unterschied von Attrubit und Übergabe von Self an Klasse

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

leider habe ich für den Betreff keine bessere Aussage gefunden. Nachfolgend stelle ich euch insgesamt drei Varianten vor, wie ich mit QThread() arbeite. Vielleicht kommen wir ohne lauffähigen Quelltext aus. Mir geht es um das Verständnis.

Variante 1
Hier übergebe ich der QThread()-Klasse das self,. Zusätzlich speichere ich einen Verweis auf den QThread(), nachdem dieser erstellt wurde. Schließlich wollen wir nicht, dass Python nach dem Verlassen der Methode aufräumt und somit die Threads beseitigt.

Code: Alles auswählen

[...]
        self.task_thread = QThread(self)
        self.task_thread.work.moveToThread(self.task_thread)
[...]
Bei der ersten Variante bekomme ich beim Schließen der Eltern-Klasse folgende Meldung:
QThread: Destroyed while thread is still running
Variante 2
Im Unterschied zur ersten Variante wird der QThread() in eine lokale Variabel gespeichert, jedoch übergebe ich hier den Self, in der Hoffnung, Paython räumt nicht auf, sondern behält den Thread.

Code: Alles auswählen

[...]
        task_thread = QThread(self)
        task_thread.work.moveToThread(task_thread)
[...]
Bei der zweiten Variante bekomme ich ebenfalls beim Schließen der Eltern-Klasse folgende Meldung:
QThread: Destroyed while thread is still running
Variante 3
Hier übergebe ich dem QThread() keinen Self, sondern speichere diesen nur in einem Attribut. Dadurch erhalte ich eine Referenz / einen Verweis auf den QThread().

Code: Alles auswählen

[...]
        self.task_thread = QThread()
        self.task_thread.work.moveToThread(self.task_thread)
[...]
Hier bekomme ich keinerlei Meldungen - alles scheint reibungslos zu klappen.

Nun meine Frage. Wieso schlagen die ersten beiden Varianten fehl und die dritte Variante klappt? Meine Vermutung geht dahin, dass durch die Übergabe des Selfs an den QThread() dieser nicht länger "gehalten" wird. Aber sicher bin ich mir da nicht.
Astorek
User
Beiträge: 72
Registriert: Samstag 24. Januar 2009, 15:06
Kontaktdaten:

Ich kenne QThread leider nicht, aber mal schauen, wie weit ich helfen kann^^.

Ich gehe davon aus, dass die Befehle alle in einer Klasse (bzw. Methode) ausgeführt werden, richtig?

Wenn ja, ist bei Variante 2 klar, wieso das nicht klappt: Der Garbage Collector findet spätestens beim Verlassen der Methode keinen Verweis mehr auf das, was auch immer QThread zurückgibt, und schießt ihn dann ab. Genauso, wie ein Garbage Collector eben arbeitet^^.

Bei der ersten Variante mutmaße ich: Wenn ich die Doku richtig verstanden habe (nur überflogen), übergibst du beim ersten Argument ein Objekt, dass die Methoden zum z.B. löschen oder ausführen des Threads haben sollte. Existieren aber diese Methoden nicht, tja... Dann passiert auch immer das, was in Qt definiert ist, und das weiß ich schlicht nicht^^. Sagt zumindest diese Seite hier (dessen Implementierung von "self.__del__" ich zwar etwas kritisch sehe - ich glaube kaum, dass man das so macht wie dort gezeigt - aber zum Heranziehen, wie QThreads im Allgemeinen funktionieren, taugt das wohl^^).
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: ein laufender Thread hat ja eine Referenz, nämlich der Kontext, in dem der Thread läuft. Die Befürchtung, dass der vom GC abgeräumt wird, ist also unbegründet. Deshalb unterscheiden sich Variante 1 und 2 auch nicht. Die Begründung bei Variante 2 ist falsch, da Du ja nur eine Referenz auf ein das Objekt self an den Thread bindest, was nicht das abräumen verhindert, denn sonst könnte ja kein Objekt abgeräumt werden, weil sie immer irgendwelche Referenzen (und sei es nur auf None) enthalten.

Wenn Du die Dokumentation zu QThread liest, wird klar, was der erste Parameter bedeutet: `parent`, also ein Objekt, das existieren muß, damit der Thread laufen kann. Wenn Du dem Thread den Owner unter dem Hintern wegziehst, ist QT zu recht beleidigt.

Bleibt die Frage: Was willst Du eigentlich mit dem Thread machen? Wie sind die Abhängigkeiten?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Siriuse und @Astorek: Zunächst einmal vielen Dank. Nun, bei mir klappt jedoch die Variante 3 problemlos. Ich sitze gerade mit dem Smartphone hier, daher kann ich leider keinen Ausschnitt aus meinem Quelltext zeigen. Später kann ich es euchbgerne zeigen. Aber ich versuche es mal mit dem Beschreiben. Ich benutze die Threads, um mit der Datenbank zu arbeiten - zum Beispiel um die TreeView oder ComboBox zu befüllen. Es folgt allgemein folgendermaßen ab: Es gibt in meiner Klasse, die für View zuständig, einige Methoden, die die Threads starten. Beim erstellen und starten der Threads (manchmal können es auch bis zu 8 Threads werden, schließlich haben bestimmte GUIs 8 ComboBoxen drauf) werden diese Threads in eine Liste hinzugefügt. Sobald die GUI beendet wird, werden die einzelnen Threads in der Liste im closeEvent() mittels der For-Schleife beendet. Und genau an dieser Stelle kam diese eine Meldung, dass ein Thread zerstört wurde, obwohl er noch aktiv ist. Aber durch die dritte Variante bekomme ich die Meldung nicht mehr und pythonw.exe hängt sich auch hin und wieder nicht mehr auf.

Frage: Reicht es nicht, wenn der Thread in einem Attribut gespeichert wird? Ich meine, dadurch bleibt der Thread auch außerhalb des Gültigkeitskeitbereiches der Methode erhalten. Denn ich übergebe beispielsweise einer QLabel() auch keinen self, sondern speichere diese Klasse auch als Attribut. Ab wann genau übergibt man den self?
Zuletzt geändert von Sophus am Dienstag 3. April 2018, 08:25, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Sirius3: ich benutze QObjects (in C++) ohne parents. Da wird nicht gemeckert. Insofern bin ich mir nicht so sicher wie du, was da das unterscheidliche verhalten angeht.

Was aber sicher ist, das hier ein anderer Fehler vorliegt. Die Warnung kommt ja nicht von ungefähr. Ein QThread-Objekt repräsentiert einen OS Thread. Die Kopplung ist aber nicht so fest, wie Sophus das annimmt. Nur weil das QThread Objekt entfernt wird, ist der OS Thread noch lange nicht beendet. Sondern reitet in dem Moment potentiell auf freigegebenen Speicher rum. Was krachen kann, oder sonst wie geartete Probleme erzeugt.

Darum sollte der QThread sich immer selbst freigeben, basierend auf dem finished-Signal. Wie im destruktor klar beschrieben:

http://doc.qt.io/qt-5/qthread.html#dtor.QThread


Wie sich das nun allerdings mit pyQt und dessen Speichersemantik verhält ist mir nicht ganz klar. Gegebenenfalls ist das hier ein __del__ wert, mit dem die umgebene Klasse wartet, bis der Worker beendet und der Thread das finished Signal verschickt hat.
Antworten