I'm trying to create basis for my program, there is Main Window with button that runs new thread for progress bar with calculation button, and calculation is in new thread also. First run of calculation is OK, but when I press "Open calculation bar" for the second time program crashes. I've tried to catch where's error using debug, but when debugging everything work fine. I assume I'm using QThread wrong... How to improve my code?
# -*- coding: utf-8 -*-
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QThread
from PyQt5 import QtWidgets
from sys import exit, argv
from time import sleep
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.open_progress_bar_button = QtWidgets.QPushButton('Open calculate bar', self)
self.open_progress_bar_button.resize(self.open_progress_bar_button.sizeHint())
self.open_progress_bar_button.move(100, 50)
self.open_progress_bar_button.clicked.connect(self.run_calculation_thread)
self.setGeometry(500, 300, 300, 100)
self.setWindowTitle('MainWindow')
self.show()
def run_calculation_thread(self):
self.open_progress_bar_button.setEnabled(False)
self.progress_bar_thread = QThread()
self.calculation_progress_bar = CalculationProgressBar()
self.calculation_progress_bar.moveToThread(self.progress_bar_thread)
QtWidgets.qApp.aboutToQuit.connect(self.progress_bar_thread.quit)
self.progress_bar_thread.start()
self.calculation_progress_bar.close_progress_bar.connect(self.on_close_create_progress_bar_thread)
def on_close_create_progress_bar_thread(self):
self.progress_bar_thread.terminate() # maybe error is here
self.open_progress_bar_button.setEnabled(True)
class CalculationProgressBar(QtWidgets.QWidget):
request_calculation = pyqtSignal()
close_progress_bar = pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
self.calculation_thread = QThread()
self.calculation = Calculation()
self.calculation.notify_progress.connect(self.on_progress)
self.calculation.calculation_done.connect(self.on_finish)
self.request_calculation.connect(self.calculation.calculate)
self.calculation_thread.started.connect(self.calculation.start)
self.calculation.moveToThread(self.calculation_thread)
QtWidgets.qApp.aboutToQuit.connect(self.calculation_thread.quit)
self.calculation_thread.start()
self.setup_gui()
self.show()
def setup_gui(self):
l = QtWidgets.QVBoxLayout(self)
self.progress_bar = QtWidgets.QProgressBar(self)
self.progress_bar.setTextVisible(False)
self.progress_bar.setRange(0, 100)
l.addWidget(self.progress_bar)
self.open_progress_bar_button = QtWidgets.QPushButton("Calculate", self, clicked=self.tables_creation_requested)
l.addWidget(self.open_progress_bar_button)
self.setFixedWidth(300)
def on_progress(self, i):
self.progress_bar.setValue(i)
def on_finish(self):
self.close()
self.close_progress_bar.emit()
@pyqtSlot()
def tables_creation_requested(self):
self.request_calculation.emit()
self.open_progress_bar_button.setEnabled(False)
class Calculation(QObject):
notify_progress = pyqtSignal(int)
calculation_done = pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
@pyqtSlot()
def start(self):
print("Ready to calculate")
@pyqtSlot()
def calculate(self):
for i in range(2):
self.notify_progress.emit(i*50)
sleep(1)
self.calculation_done.emit()
if __name__ == "__main__":
application = QtWidgets.QApplication(argv)
main_window = MainWindow()
main_window.show()
exit(application.exec_())
Why are you creating 2 threads?
The task of the CalculationProgressBar is not heavy since its task is only to show data that it receives from another thread, besides that the GUI can not move to another thread. You just need to create a single thread where an object of the Calculation class will live.
It is not necessary to connect the started signal to start the task, it is enough to call the signal with a signal as shows. On the other hand it is not necessary to create a CalculationProgressBar every time the button is pressed, it is best to reuse it.
Considering the above the solution is:
# -*- coding: utf-8 -*-
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QThread
from PyQt5 import QtWidgets
from sys import exit, argv
from time import sleep
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.open_progress_bar_button = QtWidgets.QPushButton('Open calculate bar', self)
self.open_progress_bar_button.resize(self.open_progress_bar_button.sizeHint())
self.open_progress_bar_button.move(100, 50)
self.open_progress_bar_button.clicked.connect(self.run_calculation_thread)
self.calculation_progress_bar = CalculationProgressBar()
self.calculation_progress_bar.close_progress_bar.connect(self.on_close_create_progress_bar_thread)
self.setGeometry(500, 300, 300, 100)
self.setWindowTitle('MainWindow')
self.show()
def run_calculation_thread(self):
self.open_progress_bar_button.setEnabled(False)
self.calculation_progress_bar.progress_bar.reset()
self.calculation_progress_bar.show()
def on_close_create_progress_bar_thread(self):
self.open_progress_bar_button.setEnabled(True)
class CalculationProgressBar(QtWidgets.QWidget):
request_calculation = pyqtSignal()
close_progress_bar = pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
self.calculation_thread = QThread(self)
self.calculation_thread.start()
self.calculation = Calculation()
self.calculation.moveToThread(self.calculation_thread)
self.calculation.notify_progress.connect(self.on_progress)
self.calculation.calculation_done.connect(self.on_finish)
self.request_calculation.connect(self.calculation.calculate)
QtWidgets.qApp.aboutToQuit.connect(self.calculation_thread.quit)
self.setup_gui()
def setup_gui(self):
l = QtWidgets.QVBoxLayout(self)
self.progress_bar = QtWidgets.QProgressBar(self)
self.progress_bar.setTextVisible(False)
self.progress_bar.setRange(0, 100)
l.addWidget(self.progress_bar)
self.open_progress_bar_button = QtWidgets.QPushButton("Calculate", self, clicked=self.tables_creation_requested)
l.addWidget(self.open_progress_bar_button)
self.setFixedWidth(300)
def reset(self):
self.progress_bar.setValue(0)
@pyqtSlot(int)
def on_progress(self, i):
self.progress_bar.setValue(i)
@pyqtSlot()
def on_finish(self):
self.hide()
self.close_progress_bar.emit()
self.open_progress_bar_button.setEnabled(True)
@pyqtSlot()
def tables_creation_requested(self):
self.request_calculation.emit()
self.open_progress_bar_button.setEnabled(False)
class Calculation(QObject):
notify_progress = pyqtSignal(int)
calculation_done = pyqtSignal()
@pyqtSlot()
def calculate(self):
for i in range(3):
self.notify_progress.emit(i*50)
sleep(1)
self.calculation_done.emit()
if __name__ == "__main__":
application = QtWidgets.QApplication(argv)
main_window = MainWindow()
main_window.show()
exit(application.exec_())