I have a GUI made with PyQt5, and there is a start-button on it. When the start-button is clicked, the program will start to count from 1 to 5 and then show a pop-up window to ask if the task should be interrupted. If the user clicks the "Yes" button on the pop-up window, a signal will be emitted which will instigate execution of a function (called interrupt_test
) in the worker class because of a connection I create in the main program (the module call_main_window.py). Then the task will be stopped.
I find that every time when I click the start-button, the function interrupt_test
in the worker class will be executed one more time than the last time I clicked the start-button. I know this because the message "The 'interrupt_test' function is executed." will be printed every time when the function interrupt_test
is called. And I found that this message shows up repeatedly, except for the first time I click the start-button.
Below is my code in the module called call_main_window.py:
from PyQt5 import QtCore, QtWidgets, QtGui
from main_window import Ui_Form
import time
import sys
class Stream(QtCore.QObject):
newText = QtCore.pyqtSignal(str)
def write(self, data: str):
self.newText.emit(data)
class CustomSignal(QtCore.QObject):
toInterrupt = QtCore.pyqtSignal() # line 15
# toInterrupt = QtCore.pyqtSignal(bool)
toCallMsgBox = QtCore.pyqtSignal()
class Worker(QtCore.QRunnable):
def __init__(self, mutex: QtCore.QMutex, cond: QtCore.QWaitCondition):
super(Worker, self).__init__()
self.customSignal = CustomSignal()
self.mutex = mutex
self.cond = cond
self.shouldInterruptTest = False
def start(self):
for i in range(1, 11):
if i == 6:
self.mutex.lock()
self.customSignal.toCallMsgBox.emit()
self.cond.wait(self.mutex)
self.mutex.unlock()
if self.shouldInterruptTest:
sys.stdout.write(f'The test is interrupted.\n')
break
else:
sys.stdout.write(f'{i}\n')
time.sleep(1)
def interrupt_test(self, shouldInterruptTest: bool):
self.shouldInterruptTest = shouldInterruptTest
sys.stdout.write(f'The \'interrupt_test\' function is executed.\n')
class MyMainWindow(QtWidgets.QWidget, Ui_Form):
def __init__(self, parent = None):
super(MyMainWindow, self).__init__(parent)
self.setupUi(self)
self.mutex = QtCore.QMutex()
self.cond = QtCore.QWaitCondition()
self.customSignal = CustomSignal()
self.pushButtonStart.clicked.connect(self.start_working)
self.destroyed.connect(sys.exit)
sys.stdout = Stream(newText = self.update_text)
def start_working(self):
worker = Worker(mutex = self.mutex, cond = self.cond)
worker.customSignal.toCallMsgBox.connect(self.call_msg_box)
self.customSignal.toInterrupt.connect(lambda: worker.interrupt_test(True)) # line 62
# self.customSignal.toInterrupt.connect(worker.interrupt_test)
self.threadPool = QtCore.QThreadPool()
self.threadPool.start(worker.start)
def call_msg_box(self):
sys.stdout.write('call_msg_box is called\n')
msg = QtWidgets.QMessageBox()
msg.setIcon(QtWidgets.QMessageBox.Question)
msg.setText('Do you want to stop the test?')
msg.setWindowTitle('Info')
msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
self.mutex.lock()
reply = msg.exec_()
if reply == QtWidgets.QMessageBox.Yes:
self.customSignal.toInterrupt.emit()
# self.customSignal.toInterrupt.emit(True)
self.cond.wakeAll()
self.mutex.unlock()
def update_text(self, data: str):
cursor = self.plainTextEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(data)
self.plainTextEdit.setTextCursor(cursor)
self.plainTextEdit.ensureCursorVisible()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
Below is the code for GUI in another module called main_window.py:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(300, 300)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setObjectName("gridLayout")
self.plainTextEdit = QtWidgets.QPlainTextEdit(Form)
self.plainTextEdit.setObjectName("plainTextEdit")
self.gridLayout.addWidget(self.plainTextEdit, 0, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(500, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.pushButtonStart = QtWidgets.QPushButton(Form)
self.pushButtonStart.setObjectName("pushButtonStart")
self.gridLayout.addWidget(self.pushButtonStart, 1, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButtonStart.setText(_translate("Form", "Start"))
I think the connection in line 62 of the call_main_window.py module is not automatically disconnected when the worker thread is finished. This problem can be fixed if I mark line 15 and 62 of the call_main_window.py module and unmark line 16 and 63. But I don't understand how line 15 and 62 will instigate the problem I come across. Can anyone kindly explain it to me?