3

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

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
greendino
  • 416
  • 3
  • 17
  • also this solution https://stackoverflow.com/questions/31983412/code-freezes-on-trying-to-open-qdialog by using nativedialog doesn't work for me either – greendino Oct 26 '20 at 05:18
  • What is the point of continuously creating a DataProcess instance every one second? – musicamante Oct 26 '20 at 05:22
  • as i mention. its a minimal reproducible example. i embed watch and database statuses in my real world app which update every 1 second ofc. the problem only exist in pyside2 not in pyqt5. i don't want to be bound for licensing yet. I'm still newbie – greendino Oct 26 '20 at 05:23
  • @musicamante I also read your answered question here https://stackoverflow.com/questions/63177758/pyqt5-qfiledialog-closes-when-filename-clicked?rq=1. I've tried that and still no luck for me. sorry English is my second language – greendino Oct 26 '20 at 05:44
  • Sorry, but that doesn't really answer the question. If you need to do a time consuming task every 1 second, then use `time.sleep` or, better, `self.sleep` (using the QThread function). I don't use pyside, and I cannot reproduce your problem, but I'd start from that at first. If you need to control if the repetition of a task should actually happen, then add a python `Queue()` object to the thread instance within its `__init__` and check for that each time the cycle is repeated in a `while True` cycle. – musicamante Oct 26 '20 at 05:56
  • I put QtCore.QThread.msleep() on my pyside2 minimal reproducible code. and it crashes instantly. I can't use QObject since it gonna ruin all of my code flow. I need to spend an extra week just refactoring all 2000 lines (I'm not kidding). then this site https://www.geeksforgeeks.org/migrate-pyqt5-app-to-pyside2/ is purely wrong then? it's not only 3 steps to migrate to pyside2 :(. there is no other documentation what so ever for migrating. i do have feeling this is a bug on pyside2. since it totally work on pyqt5 – greendino Oct 26 '20 at 06:03
  • I'm no expert on threading, but AFAIK you normally don't need to use QObject for threading (also, have you considered using QRunnable?). I seem to remember something related to threading and QFIleDialog's file system model, but unfortunately I can't recall it. Besides that, you need to consider that, while *in normal conditions* changing the imports from `PyQt5` to `PySide2` is usually enough, there are some specific and uncommon situations for which the code would crash or not behave as it did. I'd suggest you two things: 1. don't recreate a new `DataProcess` instance everytime, but try to -> – musicamante Oct 26 '20 at 06:35
  • -> reuse it (or use QRunnable instances); 2. run your code from a shell/command prompt to see if there is more valuable result from the crash. – musicamante Oct 26 '20 at 06:36
  • @musicamante1)it doesn't make sense ain't it to create a digital clock with QRunnable? 2) I already use the basic command prompt command. no special shell here (e.g 'python test.py') – greendino Oct 26 '20 at 10:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/223644/discussion-between-lone-coder-and-musicamante). – greendino Oct 26 '20 at 17:12

1 Answers1

2

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.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • but my code runs perfectly fine both on PyQt5 and QtPy without lagging or warning. and yes you're right I kinda spamming into it every second without waiting for the thread to finish. but that way I find my code can retrieve data more lively and way faster than waiting. that's why I implement it like that. [edit:] I did try to deleteLater() but still gave me the same error `QThread: Destroyed while thread is still running`. i also increase the timer interval with no luck – greendino Oct 26 '20 at 18:06
  • 2
    @lone_coder I explained why that is: you must give the thread objects a parent. Did you try my solution? It works perfectly fine for me, and it hardly changes the logic of your code at all. – ekhumoro Oct 26 '20 at 18:11
  • uh oh you didn't try to open QFileDialog? that's why its working. mine also work without QFileDialog. when you open the file dialog long enough it will close the app – greendino Oct 26 '20 at 18:12
  • 2
    Yes, I did open the file-dialog. If you're getting those "QThread: Destroyed" messages, it means you aren't giving the thread objects a parent. Please use the ***exact code*** given in my answer. I know it works, because I tested it very carefully. – ekhumoro Oct 26 '20 at 18:15
  • 1
    oh my, you're right. there's a loading icon on mouse pointer icon once in a while. but I think its okay. thankyou very much sir. huge respect! – greendino Oct 26 '20 at 18:20
  • 2
    @lone_coder You're welcome - good luck with your project! – ekhumoro Oct 26 '20 at 18:21