QClose Event & QThread

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Hi Zusammen,

bei folgendem Problem komme ich nicht weiter: Dieses Mainframe hier:

Code: Alles auswählen

class UktvMainframe(QtWidgets.QMainWindow):
    def __init__(self, parent=None, logger=None):
        super().__init__(parent)
        self.error_dialog = QtWidgets.QErrorMessage()
        self.logger = logging.getLogger(__name__)
        self.ui = uic.loadUi(r"C:\Users\Marc\PYTHON_DEVELOPMENT\PLAYOUT_APPLICATIONS\GUI\uktv_alarmingtool_mainframe.ui", self)
        self.setFixedWidth(910)
        self.setFixedHeight(755)

        self.setAttribute(Qt.WA_DeleteOnClose)
wird durch das Hauptframe meiner Anwendung geöffnet. Also der gezeigte Code ist NICHT das Hauptframe. Jedenfalls wird in diesem Frame ein Thread gestartet, der nach Ablauf einer Zeit eine mp3 (Alarm) abgespielt, aber selbst in einem Loop läuft. Also er kommt selbst nicht zum Ende.:

Code: Alles auswählen

self.init_alarm_gen = AG.AlarmGenerator(raw_pts_format_lst, self.ui.alarm_in_sec, self.ui.refresh_data, self.ui.roi_alarms_del)
self.init_alarm_gen.moveToThread(self.worker_alarm_gen)
            
self.init_alarm_gen.alarm_msg.connect(self.displayAlarmLog)         # Contains alarm for ROI 
self.init_alarm_gen.CounterChange.connect(self.countChange)         # Final Countdown for ROI
self.init_alarm_gen.all_alarms.connect(self.update_all_alarms)      # Counter for exported ROI Alamrs
self.init_alarm_gen.auto_refresh_data.connect(self.recall_app)      # work done
self.init_alarm_gen.killthread.connect(self.update_killthread_roi)
self.init_alarm_gen.start()
Mein Problem ist, wenn das oben gezeigte Frame über das X geschlossen wird, dann läuft der Thread munter weiter und wird NICHT geschlossen. Ich hab in der offiziellen Dokumentation nachgeschaut:
https://doc.qt.io/qtforpython-5/PySide2 ... Event.html

und dort das Flag self.setAttribute(Qt.WA_DeleteOnClose) gesetzt, was leider nicht zu dem gewünschten Ergebnis führt, dass AUCH der Thread geschlossen wird, sobald das Frame beendet wird. Probiert habe ich auch, die closeEvent Methode zu überschreiben. Leider ist die Mechanik einen Thread hier mit thread_worker.quit() & wait() zu beenden, eher Fehlerhaft. Erst wenn ich das Hauptframe der gesamten Applikation schließe, ist logischerweise der Thread auch Reif für die Garbage Collection. Ich wünsche mir aber, dass es schon in dem Unterframe geschieht und nicht erst das Signal des Hauptframes nötig ist.

Code: Alles auswählen

def closeEvent(self,event):
        sys.exit() # Es soll nicht alles geschlossen werden, nur der Thread.
Habt ihr einen Tipp? VIelen Dank
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das WA_DeleteOnClose macht nicht, was du glaubst, dass es tut. Ich denke, das kannst du dir sparen.

Und mit deinen Fragmenten hier wird mir nicht klar, was da eigentlich wie ineinander greift. Der Thread muss sich selbst beenden, anders geht es nicht, ausser man beendet das ganze Programm. Dazu kannst du doch einfach dessen exit-Slot aus deinem Main-Thread aufrufen (per Signal), von welcher Stelle aus auch immer. Auch aus dem MainWindow-Close-Event.
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

__deets__ hat geschrieben: Freitag 20. August 2021, 17:08 Das WA_DeleteOnClose macht nicht, was du glaubst, dass es tut. Ich denke, das kannst du dir sparen.

Ich glaube, das Flag sollte das machen, was in der Dokumentation steht? Oder sehe ich das falsch? Klingt theoretisch genau nach dem, was ich brauchen würde:
If you want the widget to be deleted when it is closed, create it with the WA_DeleteOnClose flag. This is very useful for independent top-level windows in a multi-window application.

Und mit deinen Fragmenten hier wird mir nicht klar, was da eigentlich wie ineinander greift. Der Thread muss sich selbst beenden, anders geht es nicht, ausser man beendet das ganze Programm. Dazu kannst du doch einfach dessen exit-Slot aus deinem Main-Thread aufrufen (per Signal), von welcher Stelle aus auch immer. Auch aus dem MainWindow-Close-Event.
Wie meinst du das genau? Das ganze Programm kann ich ja einfach mit sys.exit() beenden. Könntest du mir das mit dem exit Slot genauer erklären?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der thread hat einen slot. Den musst du via signal triggern. Wie sonst auch bei allen Dingen Qt.
m.g.o.d
User
Beiträge: 75
Registriert: Samstag 4. April 2020, 13:17

Ich hab noch ein working example gemacht, um zu verdeutlichen, was ich gerne erreichen möchte:

Hier die Mainapplication:

Code: Alles auswählen

import PyQt5, sys
from PyQt5 import QtWidgets, QtCore, uic, QtGui
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtCore import QThread, pyqtSignal

import working_example_childframe

class MainApplication(QtWidgets.QMainWindow):
    def __init__(self, parent=None, logger=None):
        super().__init__(parent)
        self.error_dialog = QtWidgets.QErrorMessage()
        self.ui = uic.loadUi(r"C:\Users\Marc\working_example\working_example_main.ui", self)
        self.ui.CmdStartChildFrame.clicked.connect(self.clicked_childframe)

    def clicked_childframe(self):
        self.init_childframe = working_example_childframe.ChildApplication()
        self.init_childframe.show() 

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    dialog = MainApplication()
    dialog.show()
    sys.exit(app.exec_())
Die hat nur einen Button, der ein Unterprogramm (ChildApplication) aufruft, welches so ausschaut:

Code: Alles auswählen

import PyQt5, sys
from PyQt5 import QtWidgets, QtCore, uic, QtGui
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtCore import QThread, pyqtSignal
from time import sleep



class ChildApplication(QtWidgets.QMainWindow):
    def __init__(self, parent=None, logger=None):
        super().__init__(parent)
        self.error_dialog = QtWidgets.QErrorMessage()
        self.ui = uic.loadUi(r"C:\Users\Marc\working_example\working_example_child.ui", self)
        self.ui.CmdStartWorkerThread.clicked.connect(self.clicked_thread_start)
        self.dummy_thread_worker= QThread()

    def clicked_thread_start(self):
        """ Start a dummy thread with runs in a loop"""
        self.init_dummy_thread = DummyThread() 
        self.init_dummy_thread.movetoThread(self.dummy_thread_worker)
        self.init_dummy_thread.start() 


class DummyThread(QThread):
    def __init__(self):
        super(DummyThread, self).__init__()

    @pyqtSlot()
    def run(self):
        while True:
            sleep(10)
Beende ich nun das Childframe durch X (was das Close Event auslöst), dann wartet der DummyThread fröhlich weiter. Ich möchte aber irgendwie, dass das beenden des Childframes auch den Thread beendet. Das geht ja z.B. auch durch ein return Argument, was mit einer Bedingung verknüpft wäre?! Habt ihr eine Idee, wie das zu bewerkstelligen ist?

Nachtrag:
Überschreibe ich in der ChildApplication die closeEvent Methode, funktioniert das leider auch alles nicht und ich sehe keine Möglichkeit, wie ich in den Thread einen Parameter von hier schicken kann, der ein return Statement auslösen würde...

Code: Alles auswählen

    
    def closeEvent(self,event):
        #self.dummy_thread_worker.exit()   -> geht nicht
        #self.init_dummy_thread.quit()    -> führt zum Absturz
        #self.init_dummy_thread.wait()    -> dito.
        print("exit")
        event.accept()
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

So gehts nicht. Sinn und Zweck des ganzen worker gedöns ist, dass man NICHT die run-Methode überlädt. Und Arbeit in Form von Signalen bekommt, deren ausgelöste slots dann aber auch nur endlich viel tun.

Wenn du stattdessen eine solche while-Schleife bastelst, dann muss die eben selber prüfen, ob der thread noch laufen soll, oder nicht.

Zu guter letzt: es klingt ein bisschen so, als ob du denkst, ein thread sei ein Objekt. Und wenn das abgerissen wird, wäre der thread beendet. Das stimmt nicht. Steht so übrigens auch sehr deutlich in der QThread Doku. Ein Thread läuft. Das QThread-Objekt ist nur eine etwas krepelige Schnittstelle, aber das war es auch.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Noch eine Beobachtung: du hast die Benamung verhunzt. Worker ist ein QObject, das zu einem Thread gehört. Du nennst aber den Thread worker, und den Worker Thread.

Das man den worker erst auf eine Click anlegt, ist auch nicht sinnvoll. Den legt man zusammen mit dem thread an. Und started den Thread auch. Der Click stellt ein Signal dar, und das verbindet man mir einem Slot auf dem worker. Durch die thread ownership ist das dann automatisch eine queued connection, und der thread mainloop führt den dann aus. Wenn man ihn nicht wie du durch run Überladung deaktiviert hat.

Das ganze Thema threading und Qt sitzt in meinen Augen noch nicht, und ist ja immer und immer wieder Thema hier. Ich würde dir da nachdrücklich nochmal zum durcharbeiten der Qt Dokumentation und zum erstellen von kleinen Beispielen raten, anhand derer du das alles mal ausprobierst. Denn das in deine große Anwendung zu Stricken scheint noch etwas gewagt.
Antworten