每当从 PyQt5 迁移到 Pyside2 后调用 QFileDialog 时,QThread 关闭

QThread closes whenever QFileDialog Called After Migrating from PyQt5 to Pyside2

首先,我目前正在将我的源代码从 PyQt5 迁移到 PySide2,这需要我更改一些语法。正如这个 site 所说,从 PyQt 迁移到 Pyside2 只需要做 3 件事。

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.

但无论如何,后来当我尝试 运行 我的代码时,它给了我以下错误:

QThread: Destroyed while thread is still running

我有超过 2000 行代码,除了我最后一个尝试调用 QFileDialog 的操作之外,我无法确定是什么原因造成的,这应该不是问题(我已经用 PyQt 导入测试了这个,没有问题,也没有任何警告)。但在 PySide2 中,它绝对可能是它的原因。我查了一下this,他和我的问题完全不一样。我没有尝试从不同的线程调用 QFileDialog。

这是我在 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_())

这是在将导入相应地重命名为 PySide2 并将 'pyqtsignal' 重命名为 'Signal'

之后 PySide2 中的非工作文件
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_())

所以在创建这个最小示例之后,我意识到 PySide QFileDialog 使 QThread 停止,而 PyQt QFileDialog 不会冻结主线程。在类似的语法体系结构中,我能做些什么来处理这个问题吗? (例如不使用“movetothread”或“QObject”)

问题是每次创建新线程时你都在覆盖self.DataProcess,这可能导致之前的对象在Qt有机会之前garbage-collected被Python删除它。如果 Qt 试图删除一个不再存在的对象,这会导致 core-dump。这类问题在 PyQt 和 PySide 中都很常见,通常是由于没有保持对依赖对象的正确引用而引起的。正常的解决方案是确保为受影响的对象指定一个父对象,并在必要时在适当的时候显式删除它们。

这是修复示例的一种方法:

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()

很难确切地说出为什么 PySide 在这种特殊情况下的行为与 PyQt 不同。它通常归结为两种实现之间的 low-level 差异。可能存在影响 PyQt 但不影响 PySide 的等效情况。但是,如果您仔细管理对象引用和清理,通常可以消除此类差异。