QThread closes whenever QFileDialog Called After Migrating from PyQt5 to Pyside2

别等时光非礼了梦想. 提交于 2020-12-04 04:59:23

问题


First of all, I'm currently migrating my source code from PyQt5 to PySide2 which requires me to change some of the syntaxes. As this site said that it only needs 3 things to do migrate from PyQt to Pyside2.

1.app.exec_. exec_ was used as exec is a Python2 keyword. Under Python3, PyQt5 allows the use of exec but not PySide2.

2.Under PyQt5 it’s QtCore.pyqtSignal and QtCore.pyqtSlot and under PySide2 it’s QtCore.Signal and QtCore.Slot .

3.loading Ui files.

But anyway later on when I tried to run my code it gave me this following error:

QThread: Destroyed while thread is still running

I had more than 2000 lines of code and I cannot determine which is the cause of this other than my last action which is trying to call QFileDialog which shouldn't be a problem (I've tested this with PyQt import and there's no problem and no warning at all). But in PySide2 it definitely might be the cause of it. I look up into this, he doesn't have the same problem as mine exactly. I'm not trying to call QFileDialog from different thread.

this is the minimal reproducible example of my working code in PyQt5:

import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random

class MyWidget(QtWidgets.QWidget):

    def __init__(self):

        QtWidgets.QWidget.__init__(self)

        self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
        self.button = QtWidgets.QPushButton("Open File")
        self.labelFile = QtWidgets.QLabel("empty")
        self.labelData = QtWidgets.QLabel("None")
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.button)
        self.layout.addWidget(self.labelFile)
        self.layout.addWidget(self.labelData)
        self.setLayout(self.layout)
        self.button.clicked.connect(self.open_file)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_data_value)
        timer.start(1000)

    def open_file(self):
        x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
        self.labelFile.setText(x[0])

    def update_data_value(self):
        self.DataProcess = DataProcess()
        self.DataProcess.progress.connect(self.update_data_label)
        self.DataProcess.start()

    def update_data_label(self,x):
        self.labelData.setText(str(x[0]))

class DataProcess(QtCore.QThread):
    progress = QtCore.pyqtSignal(object)
    def __init__(self):
        QtCore.QThread.__init__(self)    
    
    def run(self):
        x = random.randint(1,100)
        self.progress.emit([str(x)+ " from thread"])

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())

and this is the non-working one in PySide2 after renaming import accordingly to PySide2 also renaming 'pyqtsignal' to 'Signal'

import sys
import os
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random

class MyWidget(QtWidgets.QWidget):

    def __init__(self):

        QtWidgets.QWidget.__init__(self)

        self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
        self.button = QtWidgets.QPushButton("Open File")
        self.labelFile = QtWidgets.QLabel("empty")
        self.labelData = QtWidgets.QLabel("None")
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.button)
        self.layout.addWidget(self.labelFile)
        self.layout.addWidget(self.labelData)
        self.setLayout(self.layout)
        self.button.clicked.connect(self.open_file)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_data_value)
        timer.start(1000)

    def open_file(self):
        x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
        self.labelFile.setText(x[0])

    def update_data_value(self):
        self.DataProcess = DataProcess()
        self.DataProcess.progress.connect(self.update_data_label)
        self.DataProcess.start()

    def update_data_label(self,x):
        self.labelData.setText(str(x[0]))

class DataProcess(QtCore.QThread):
    progress = QtCore.Signal(object)
    def __init__(self):
        QtCore.QThread.__init__(self)    
    
    def run(self):
        x = random.randint(1,100)
        self.progress.emit([str(x)+ " from thread"])

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())

so after creating this minimal example, I realized that PySide QFileDialog makes the QThread stop while PyQt QFileDialog doesn't freeze the main thread. Is there anything I could do to handle this in similar syntax architecture? (e.g not using "movetothread" or "QObject")


回答1:


The problem is that you're overwriting self.DataProcess every time a new thread is created, which may cause the previous object to be garbage-collected by Python before Qt has a chance to delete it. This can result in a core-dump if Qt tries to delete an object which is no longer there. Problems of this kind are quite common in both PyQt and PySide, and are usually caused by not keeping proper references to dependant objects. The normal solution is to ensure that the affected objects are given a parent and, if necessary, explicitly delete them at an appropriate time.

Here is one way to fix your example:

class MyWidget(QtWidgets.QWidget):
    ...

    def update_data_value(self):
        # ensure the thread object has a parent
        process = DataProcess(self)
        process.progress.connect(self.update_data_label)
        process.start()

    def update_data_label(self,x):
        self.labelData.setText(str(x[0]))

class DataProcess(QtCore.QThread):
    progress = QtCore.Signal(object)
    def __init__(self, parent):
        # ensure the thread object has a parent
        QtCore.QThread.__init__(self, parent)

    def run(self):
        x = random.randint(1,100)
        self.progress.emit([str(x)+ " from thread"])
        # explicitly schedule for deletion
        self.deleteLater()

It's hard to say exactly why PySide behaves differently to PyQt in this particular case. It usually just comes down to low-level differences between the two implementations. Probably there are equivalent cases that affect PyQt but not PySide. However, if you manage object references and cleanup carefully, such differences can usually be eliminated.



来源:https://stackoverflow.com/questions/64531794/qthread-closes-whenever-qfiledialog-called-after-migrating-from-pyqt5-to-pyside2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!