QWidget method not called on destroyed signal
-
That makes sense. I also tried to override LineChart.closeEvent() but that one doesn't seem to be called either. My goal here is to somehow stop the timer from LineChart itself so I don't have to do it from the main window. So far I have tried LineChart.closeEvent and LineChart.destroyed but neither seem to work
So if I want to reuse a similar widget I don't have to remember to call the stop method from other widgets.
I also forgot to mention the error I get is:
QThread: Destroyed while thread is still running
Which is why I am trying to exit the thread on main window close
@Anonimista said in QWidget method not called on destroyed signal:
QThread: Destroyed while thread is still running
Stop the thread in the destructor of the LineChart class and wait for it to stop (https://doc.qt.io/qt-6/qthread.html#wait-1)
-
@Anonimista said in QWidget method not called on destroyed signal:
QThread: Destroyed while thread is still running
Stop the thread in the destructor of the LineChart class and wait for it to stop (https://doc.qt.io/qt-6/qthread.html#wait-1)
@jsulm said in QWidget method not called on destroyed signal:
Stop the thread in the destructor of the LineChart and wait for it to stop (https://doc.qt.io/qt-6/qthread.html#wait-1)
I haven't tested but am "concerned" that the way the code is written from Python the
window = MainWindow()
will keep a reference till after the event loop has exited (app.exec()
returns). Can one then still callQThread.wait()
with no event loop running? I don't know, OP may have to investigate. -
@jsulm It appears I don't know how to. I used LineChart.del() to call the stop() method but I still get the "QThread: Destroyed while thread is still running" error, while If i call stop() from MainWindow.closeEvent() it works fine.
-
@JonB You are right, If I use:
if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() res = app.exec() del window sys.exit(res)
LineChart.del() gets called. So now I can use del window instead of MainWindow.closeEvent(). I actually remember del being used in the official PySide6 Qml examples like
view.show() res = app.exec() # Deleting the view before it goes out of scope is required to make sure all child QML instances # are destroyed in the correct order. del view sys.exit(res)
So maybe there isn't a better way to do this from Python . Thanks to the both of you
-
-
@jsulm It appears I don't know how to. I used LineChart.del() to call the stop() method but I still get the "QThread: Destroyed while thread is still running" error, while If i call stop() from MainWindow.closeEvent() it works fine.
@Anonimista said in QWidget method not called on destroyed signal:
I used LineChart.del() to call the stop() method
Do you mean you implemented the destructor where you call stop()?
-
@jsulm I used
LineChart.__del__()
to callstop()
. I've since moved the wholestop()
body into__del__()
and usedel window
afterapp.exec()
because without it__del__()
does not get called.Here's what it looks like right now
import sys import psutil from PySide6.QtCore import (Qt, QTimer, QThread,QObject, Signal, Slot) from PySide6.QtGui import QPainter from PySide6.QtWidgets import (QMainWindow, QApplication, QWidget, QHBoxLayout, QLabel) from PySide6.QtCharts import QChart, QChartView, QLineSeries class Worker(QObject): done = Signal(int) def __init__(self, parent=None): super().__init__(parent) @Slot() def get_cpu_load(self): if QThread.currentThread().isInterruptionRequested(): return self.done.emit(psutil.cpu_percent()) class LineChart(QWidget): def __init__(self, parent=None): super().__init__(parent) self.worker = Worker() self.thread = QThread() self.worker.moveToThread(self.thread) self.thread.start() self.series = QLineSeries() for i in range(10): self.series.append(i, 0) self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) self.chart.createDefaultAxes() self.chart.setTitle('CPU load') self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.horizontal_axis = self.chart.axes(Qt.Orientation.Horizontal)[0] self.vertical_axis = self.chart.axes(Qt.Orientation.Vertical)[0] self.horizontal_axis.setLabelsVisible(False) self.vertical_axis.setLabelsVisible(False) self.vertical_axis.setMin(0) self.vertical_axis.setMax(100) self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.chart_view) self.timer = QTimer() self.timer.timeout.connect(self.worker.get_cpu_load) self.worker.done.connect(self.update_chart) self.timer.start(500) self.current_time = 19 def update_chart(self, load): self.series.append(self.current_time, load) self.current_time += 1 if self.series.count() > 20: self.series.remove(0) self.horizontal_axis.setMin(self.series.at(0).x()) self.horizontal_axis.setMax(self.series.at(self.series.count() - 1).x()) def __del__(self): print('in LineChart.__del__()') self.timer.stop() self.thread.requestInterruption() self.thread.quit() self.thread.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.chart_widget = LineChart() self.setCentralWidget(self.chart_widget) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.resize(440, 300) window.show() res = app.exec() del window sys.exit(res)
-
@jsulm I used
LineChart.__del__()
to callstop()
. I've since moved the wholestop()
body into__del__()
and usedel window
afterapp.exec()
because without it__del__()
does not get called.Here's what it looks like right now
import sys import psutil from PySide6.QtCore import (Qt, QTimer, QThread,QObject, Signal, Slot) from PySide6.QtGui import QPainter from PySide6.QtWidgets import (QMainWindow, QApplication, QWidget, QHBoxLayout, QLabel) from PySide6.QtCharts import QChart, QChartView, QLineSeries class Worker(QObject): done = Signal(int) def __init__(self, parent=None): super().__init__(parent) @Slot() def get_cpu_load(self): if QThread.currentThread().isInterruptionRequested(): return self.done.emit(psutil.cpu_percent()) class LineChart(QWidget): def __init__(self, parent=None): super().__init__(parent) self.worker = Worker() self.thread = QThread() self.worker.moveToThread(self.thread) self.thread.start() self.series = QLineSeries() for i in range(10): self.series.append(i, 0) self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) self.chart.createDefaultAxes() self.chart.setTitle('CPU load') self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.horizontal_axis = self.chart.axes(Qt.Orientation.Horizontal)[0] self.vertical_axis = self.chart.axes(Qt.Orientation.Vertical)[0] self.horizontal_axis.setLabelsVisible(False) self.vertical_axis.setLabelsVisible(False) self.vertical_axis.setMin(0) self.vertical_axis.setMax(100) self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.chart_view) self.timer = QTimer() self.timer.timeout.connect(self.worker.get_cpu_load) self.worker.done.connect(self.update_chart) self.timer.start(500) self.current_time = 19 def update_chart(self, load): self.series.append(self.current_time, load) self.current_time += 1 if self.series.count() > 20: self.series.remove(0) self.horizontal_axis.setMin(self.series.at(0).x()) self.horizontal_axis.setMax(self.series.at(self.series.count() - 1).x()) def __del__(self): print('in LineChart.__del__()') self.timer.stop() self.thread.requestInterruption() self.thread.quit() self.thread.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.chart_widget = LineChart() self.setCentralWidget(self.chart_widget) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.resize(440, 300) window.show() res = app.exec() del window sys.exit(res)
@Anonimista said in QWidget method not called on destroyed signal:
I used LineChart.del() to call stop()
Calling del() will not magically call stop(), that was my point
-
@Anonimista said in QWidget method not called on destroyed signal:
I used LineChart.del() to call stop()
Calling del() will not magically call stop(), that was my point
-
@jsulm
I presume OP means he still has the signal ondestroyed()
to call his slot which callsstop()
. And with explicit call toLineChart.del()
wherever he has it does result in the slot being called successfully. At least that's my guess...?@JonB No, I have the whole script posted in my previous comment. I copied the code from
stop()
into__del__()
and had to calldel window
at the end of the script.Since the last comment I did some more searching and I was able to use
atexit.register
to execute the QThread cleanup code so it is competely contained in theLineChart
class. Here is the current iteration:import sys import psutil import atexit from PySide6.QtCore import (Qt, QTimer, QThread,QObject, Signal, Slot) from PySide6.QtGui import QPainter from PySide6.QtWidgets import (QMainWindow, QApplication, QWidget, QHBoxLayout) from PySide6.QtCharts import QChart, QChartView, QLineSeries class Worker(QObject): done = Signal(int) def __init__(self, parent=None): super().__init__(parent) @Slot() def get_cpu_load(self): if QThread.currentThread().isInterruptionRequested(): return self.done.emit(psutil.cpu_percent()) class LineChart(QWidget): def __init__(self, parent=None): super().__init__(parent) atexit.register(self.cleanup) self.worker = Worker() self.thread = QThread() self.worker.moveToThread(self.thread) self.thread.start() self.series = QLineSeries() for i in range(10): self.series.append(i, 0) self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) self.chart.createDefaultAxes() self.chart.setTitle('CPU load') self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.horizontal_axis = self.chart.axes(Qt.Orientation.Horizontal)[0] self.vertical_axis = self.chart.axes(Qt.Orientation.Vertical)[0] self.horizontal_axis.setLabelsVisible(False) self.vertical_axis.setLabelsVisible(False) self.vertical_axis.setMin(0) self.vertical_axis.setMax(100) self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.chart_view) self.timer = QTimer() self.timer.timeout.connect(self.worker.get_cpu_load) self.worker.done.connect(self.update_chart) self.timer.start(500) self.current_time = 19 def update_chart(self, load): self.series.append(self.current_time, load) self.current_time += 1 if self.series.count() > 20: self.series.remove(0) self.horizontal_axis.setMin(self.series.at(0).x()) self.horizontal_axis.setMax(self.series.at(self.series.count() - 1).x()) def cleanup(self): print('in cleanup') self.timer.stop() self.thread.requestInterruption() self.thread.quit() self.thread.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.chart_widget = LineChart() self.setCentralWidget(self.chart_widget) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.resize(440, 300) window.show() sys.exit(app.exec())
-
@JonB No, I have the whole script posted in my previous comment. I copied the code from
stop()
into__del__()
and had to calldel window
at the end of the script.Since the last comment I did some more searching and I was able to use
atexit.register
to execute the QThread cleanup code so it is competely contained in theLineChart
class. Here is the current iteration:import sys import psutil import atexit from PySide6.QtCore import (Qt, QTimer, QThread,QObject, Signal, Slot) from PySide6.QtGui import QPainter from PySide6.QtWidgets import (QMainWindow, QApplication, QWidget, QHBoxLayout) from PySide6.QtCharts import QChart, QChartView, QLineSeries class Worker(QObject): done = Signal(int) def __init__(self, parent=None): super().__init__(parent) @Slot() def get_cpu_load(self): if QThread.currentThread().isInterruptionRequested(): return self.done.emit(psutil.cpu_percent()) class LineChart(QWidget): def __init__(self, parent=None): super().__init__(parent) atexit.register(self.cleanup) self.worker = Worker() self.thread = QThread() self.worker.moveToThread(self.thread) self.thread.start() self.series = QLineSeries() for i in range(10): self.series.append(i, 0) self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) self.chart.createDefaultAxes() self.chart.setTitle('CPU load') self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.horizontal_axis = self.chart.axes(Qt.Orientation.Horizontal)[0] self.vertical_axis = self.chart.axes(Qt.Orientation.Vertical)[0] self.horizontal_axis.setLabelsVisible(False) self.vertical_axis.setLabelsVisible(False) self.vertical_axis.setMin(0) self.vertical_axis.setMax(100) self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.chart_view) self.timer = QTimer() self.timer.timeout.connect(self.worker.get_cpu_load) self.worker.done.connect(self.update_chart) self.timer.start(500) self.current_time = 19 def update_chart(self, load): self.series.append(self.current_time, load) self.current_time += 1 if self.series.count() > 20: self.series.remove(0) self.horizontal_axis.setMin(self.series.at(0).x()) self.horizontal_axis.setMax(self.series.at(self.series.count() - 1).x()) def cleanup(self): print('in cleanup') self.timer.stop() self.thread.requestInterruption() self.thread.quit() self.thread.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.chart_widget = LineChart() self.setCentralWidget(self.chart_widget) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.resize(440, 300) window.show() sys.exit(app.exec())
atexit.register
registered functions/methods are executed on the Python interpreter termination a better solution would be to useweakref.finalize
which is called when a LineChart is garbage collected. Here is the current code:import sys import psutil import weakref from PySide6.QtCore import (Qt, QTimer, QThread,QObject, Signal, Slot) from PySide6.QtGui import QPainter from PySide6.QtWidgets import (QMainWindow, QApplication, QWidget, QHBoxLayout) from PySide6.QtCharts import QChart, QChartView, QLineSeries class Worker(QObject): done = Signal(int) def __init__(self, parent=None): super().__init__(parent) @Slot() def get_cpu_load(self): if QThread.currentThread().isInterruptionRequested(): return self.done.emit(psutil.cpu_percent()) class LineChart(QWidget): def __init__(self, parent=None): super().__init__(parent) weakref.finalize(self, self.cleanup) self.worker = Worker() self.thread = QThread() self.worker.moveToThread(self.thread) self.thread.start() self.series = QLineSeries() for i in range(10): self.series.append(i, 0) self.chart = QChart() self.chart.legend().hide() self.chart.addSeries(self.series) self.chart.createDefaultAxes() self.chart.setTitle('CPU load') self.chart.layout().setContentsMargins(0, 0, 0, 0) self.chart.setBackgroundRoundness(0) self.horizontal_axis = self.chart.axes(Qt.Orientation.Horizontal)[0] self.vertical_axis = self.chart.axes(Qt.Orientation.Vertical)[0] self.horizontal_axis.setLabelsVisible(False) self.vertical_axis.setLabelsVisible(False) self.vertical_axis.setMin(0) self.vertical_axis.setMax(100) self.chart_view = QChartView(self.chart) self.chart_view.setRenderHint(QPainter.Antialiasing) self.setLayout(QHBoxLayout()) self.layout().addWidget(self.chart_view) self.timer = QTimer() self.timer.timeout.connect(self.worker.get_cpu_load) self.worker.done.connect(self.update_chart) self.timer.start(500) self.current_time = 19 def update_chart(self, load): self.series.append(self.current_time, load) self.current_time += 1 if self.series.count() > 20: self.series.remove(0) self.horizontal_axis.setMin(self.series.at(0).x()) self.horizontal_axis.setMax(self.series.at(self.series.count() - 1).x()) def cleanup(self): print('in cleanup') self.timer.stop() self.thread.requestInterruption() self.thread.quit() self.thread.wait() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.chart_widget = LineChart() self.setCentralWidget(self.chart_widget) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.resize(440, 300) window.show() sys.exit(app.exec())