Reputation: 2058
I have an application where I run some process in a second thread. At some points during this process, given a certain condition is met, another dialog window opens, which halts the process until you confirm something. This causes the following error message:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1f9c82383d0), parent's thread is QThread(0x1f9c7ade2a0), current thread is QThread(0x1f9c8358800)
Interestingly, if you also move your cursor over the MainWindow
while the process is running, and before the new dialog pops up, it also produces this error message a couple of times:
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
Very strange. Because it only occurs if you move your cursor over the MainWindow
.
Now, in my application, I actually load an interface for the new dialog that pops up using PyQt5.uic.loadUi
, and this hasn't caused any problems. However, when I was creating the example for this post, another issue occurred, due to the fact that I was setting the layout of the new dialog during its initialization:
QObject::setParent: Cannot set parent, new parent is in a different thread
Which results in the application crashing:
Process finished with exit code -1073741819 (0xC0000005)
I'm obviously doing something wrong here regarding the threading I would guess, but I don't know what. I am especially baffled by the fact that I cannot set the layout of the new dialog during its initialization, while using loadUi
is totally fine. Here is my example code:
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (
QDialog, QApplication, QPushButton, QGridLayout, QProgressBar, QLabel
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('pass variable')
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
def run(self):
result = None
i = 0
while i < 100 and self._isRunning:
if i == np.random.randint(0, 100):
dialog = SpecialDialog()
dialog.exec_()
result = dialog.variable
time.sleep(0.01)
i += 1
self.progress.emit(i)
if i == 100:
self._success = True
self.finished.emit(result)
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('test')
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel('Result:')
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.finished.connect(self.finisher)
self.handler_thread.started.connect(self.handler.run)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def finisher(self, result):
self.result = result
self.resultLabel.setText(f'Result: {result}')
self.pbar.setValue(0)
self.handler.stop()
self.handler.progress.disconnect(self.progress)
self.handler.finished.disconnect(self.finisher)
self.handler_thread.started.disconnect(self.handler.run)
self.handler_thread.terminate()
self.handler = None
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
EDIT
I forgot to mention that I already found this post, which may be related to my problem, however, I don't undestand the reasoning of the solution in the top answer, and more importantly, I don't speak what I believe is C++.
Upvotes: 1
Views: 94
Reputation: 243897
If we analyze the case before equality happens it is the same case after that equality happens unless the initial progress is different, with this it indicates that when equality happens a signal must be sent to the GUI to ask for the information, and when you have that information, launch the same task but with an initial progress equal to what you had before the signal was emitted.
from functools import partial
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QTimer
from PyQt5.QtWidgets import (
QDialog,
QApplication,
QPushButton,
QGridLayout,
QProgressBar,
QLabel,
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("pass variable")
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal()
resultChanged = pyqtSignal(int)
requestSignal = pyqtSignal()
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
@pyqtSlot()
def start(self):
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
self.task()
@pyqtSlot()
@pyqtSlot(int)
def task(self, value=None):
if value is not None:
self.result = value
while self.iter < 100 and self._isRunning:
if self.iter == np.random.randint(0, 100):
self.requestSignal.emit()
return
time.sleep(0.01)
self.iter += 1
self.progress.emit(self.iter)
if self.iter == 100:
self._success = True
if self.result:
self.resultChanged.emit(self.result)
self.finished.emit()
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("test")
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel("Result:")
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.resultChanged.connect(self.on_result_changed)
self.handler.finished.connect(self.on_finished)
self.handler.requestSignal.connect(self.onRequestSignal)
self.handler_thread.started.connect(self.handler.start)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def onRequestSignal(self):
dialog = SpecialDialog()
dialog.exec_()
wrapper = partial(self.handler.task, dialog.variable)
QTimer.singleShot(0, wrapper)
@pyqtSlot(int)
def on_result_changed(self, result):
self.result = result
self.resultLabel.setText(f"Result: {result}")
@pyqtSlot()
def on_finished(self):
self.pbar.setValue(0)
self.handler.stop()
self.handler_thread.quit()
self.handler_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
Upvotes: 2