【问题标题】:QThread closes whenever QFileDialog Called After Migrating from PyQt5 to Pyside2每当从 PyQt5 迁移到 Pyside2 后调用 QFileDialog 时,QThread 就会关闭
【发布时间】:2021-02-08 09:29:17
【问题描述】:

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

1.app.exec_。 exec_ 被用作 exec 是 Python2 关键字。 Python3下,PyQt5允许使用exec,但不允许使用PySide2。

2.PyQt5下是QtCore.pyqtSignal和QtCore.pyqtSlot,PySide2下是QtCore.Signal和QtCore.Slot。

3.加载UI文件。

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

QThread:在线程仍在运行时销毁

我有超过 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”)

【问题讨论】:

  • 这个解决方案 stackoverflow.com/questions/31983412/… 使用 nativedialog 也不适合我
  • 每隔一秒连续创建一个DataProcess实例有什么意义?
  • 正如我所提到的。它是一个最小的可重现示例。我在我的真实世界应用程序中嵌入了手表和数据库状态,每 1 秒更新一次。该问题仅存在于 pyside2 中,而不存在于 pyqt5 中。我还不想被许可绑定。我还是新手
  • @musicamante 我还在这里阅读了您回答的问题stackoverflow.com/questions/63177758/…。我已经尝试过了,但我仍然没有运气。抱歉英语是我的第二语言
  • 对不起,但这并不能真正回答问题。如果您需要每 1 秒执行一次耗时的任务,请使用 time.sleep 或更好的 self.sleep(使用 QThread 函数)。我不使用 pyside,我无法重现您的问题,但我会从那开始。如果您需要控制任务的重复是否应该实际发生,则将 python Queue() 对象添加到其 __init__ 内的线程实例中,并在每次循环在 while True 循环中重复时检查它。

标签: python-3.x pyqt5 qthread pyside2


【解决方案1】:

问题是每次创建新线程时都会覆盖self.DataProcess,这可能会导致在Qt 有机会删除之前的对象被Python 垃圾收集。如果 Qt 试图删除不再存在的对象,这可能会导致核心转储。这类问题在 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 不同。它通常只是归结为两种实现之间的低级差异。可能存在影响 PyQt 但不影响 PySide 的等效情况。但是,如果您仔细管理对象引用和清理,通常可以消除这种差异。

【讨论】:

  • 但我的代码在 PyQt5 和 QtPy 上都运行良好,没有滞后或警告。是的,你是对的,我每秒都在向它发送垃圾邮件,而无需等待线程完成。但这样我发现我的代码可以比等待更生动、更快速地检索数据。这就是为什么我这样实现它。 [编辑:] 我确实尝试过 deleteLater() 但仍然给了我同样的错误QThread: Destroyed while thread is still running。我也增加了没有运气的计时器间隔
  • @lone_coder 我解释了原因:你必须给线程对象一个父对象。你试过我的解决方案了吗?它对我来说非常好用,而且几乎不会改变你的代码逻辑。
  • 呃哦你没有尝试打开QFileDialog?这就是它工作的原因。我的也可以在没有 QFileDialog 的情况下工作。当您打开文件对话框足够长的时间时,它将关闭应用程序
  • 是的,我确实打开了文件对话框。如果你收到那些“QThread: Destroyed”消息,这意味着你没有给线程对象一个父对象。请使用我的回答中给出的确切代码。我知道它有效,因为我非常仔细地测试过。
  • @lone_coder 不客气 - 祝你的项目好运!
猜你喜欢
  • 2019-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多