Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QThread still running when it should have already finished



  • Hi,

    I've been trying to learn how to use QThreads and although I was able to make a simple example work, I'm not able to find how to really 'finish' the thread.

    Even when the function assigned to run in the thread finishes, it seems thread keeps running somehow as told by calling the isRunning() method and also when closing the main window when I get one of those QThread: Destroyed while thread is still running messages even when the thread should have been already finished.

    You can see a very simple example here:

    from PySide2.QtWidgets import *
    from PySide2.QtCore import *
    from time import sleep
    import sys
    
    
    class Worker(QObject):
        start = Signal()
        stopping = Signal()
    
        def __init__(self, function, *args, **kwargs):
            super().__init__()
    
            self.__function = function
            self.__args = args
            self.__kwargs = kwargs
            self.start.connect(self.run)
    
        @Slot()
        def run(self):
            self.__function(*self.__args, **self.__kwargs)
    
        def stop(self):
            self.stopping.emit()
    
    
    class MainWindow(QMainWindow):
        my_thread = None
    
        def __init__(self):
            super().__init__()
            centralWidget = QWidget(self)
            windowLayout = QVBoxLayout(centralWidget)
    
            self.startBtn = QPushButton("&Start")
            self.startBtn.clicked.connect(self.startClicked)
            self.startBtn.setCheckable(True)
            self.label = QLabel()
    
            windowLayout.addWidget(self.startBtn)
            windowLayout.addWidget(self.label)
    
            self.setCentralWidget(centralWidget)
    
            self.show()
     
        def count(self, r):
            for i in r:
                self.label.setText(str(i))
                sleep(1)
            self.label.setText("Finished!")
            self.startBtn.setChecked(False)
    
        @Slot(bool)
        def startClicked(self, checked):
            if not self.startBtn.isChecked():
                self.startBtn.setChecked(True)
                msg = QMessageBox(QMessageBox.Warning, "Error", "Already running")
                msg.exec()
            else:
                self.my_thread = QThread()
                self.my_thread.start()
    
                self.worker = Worker(self.count, range(10))
                self.worker.moveToThread(self.my_thread)
                self.my_thread.finished.connect(self.worker.deleteLater)
                self.worker.start.emit()
    
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    
    sys.exit(app.exec_())
    

    As you can see there, when the QPushButton is clicked, the thread is assigned to run the MainWindow.count() method, which displays a counter in a QLabel during which the QPushButton stays checked and MainWindow.count() unchecks the QPushButton when it finishes.

    All works as expected except that the the applications crashes with QThread: Destroyed while thread is still running even when MainWindow.count() has already finished.
    I haven't added any way to stop the thread if you close the application when it's running, so I would expect that error when the count() loop is still running. But why is it still happening when that method has already finished?
    What's even worse, if I try to run the loop again by clicking on the button again after it finished one first time, the application also crashes with the same error.

    What am I doing wrong? I read similar threads in this and other forums and also read the documentation, but I'm not really able to find what I'm missing.



  • @jprey
    I see this is your first post, welcome. My first advice to anyone new asking how to use threads in Qt --- which these days seems to be the default first question asked, for unknown reasons --- is: they are difficult, they are even more difficult from Python, try not to, why do you want to use them at all, what is your concrete use case?



  • Hi @JonB, thanks for your quick reply.

    Yes, it's my first post because it's the first time I'm not able to find a solution by myself or googling.

    My concrete use case is one where I fetch paginated results from a REST API to populate a QListWidget and I want to do it without blocking the UI and showing a progress indication.

    I was able to achieve all that with Python's threading, but I also wanted to add a way to stop the thread if required (for example, if the user wants to close the application and doesn't want to wait for other threads to join and finish), which I was able to achieve easily with Qthreads and signals, but now I'm facing this other issue.

    Since you seem to discourage Qthreads, what's a better way of doing it? Should I go back to threading? Or is there an easier alternative from Qt than Qthreads?

    I should be able to post some example code more similar to my real use case, but that won't probably happen this week because, you know, Christmas and all that.

    Meanwhile, you don't see then what's the issue in my example code?


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Then stop immediately with threading. No need for that when using Qt.

    Check QNetworkAccessManager. It's asynchronous and provides you with what you expect.


  • Lifetime Qt Champion

    @jprey said in QThread still running when it should have already finished:

    Meanwhile, you don't see then what's the issue in my example code?

    Your implanting it wrongly.

    See the QThread documentation for an example of worker object implementation.

    You are mixing responsibilities of who starts and stops what.



  • @jprey
    And before you are tempted to read all about threading to try to get your proposed solution working --- which I just know will be your temptation :) --- think about what @SGaist and I are saying about threads and try doing it all with Qt's asynchronous QNetworkAccessManager instead!



  • Hi, thank you both for your tips.

    I didn't know QNetworkAccessManager, that's why I was going the Python way with requests+threading because I didn't know that other more Qtish way of doing it with QNetworkAccessManager

    I will have a look at rewriting my code with QNetworkAccessManager, which will be probably more convenient than using requests+threading.

    But it won't be until next week when I will be able to try that (for the Christmas reasons already mentioned). I hope it's OK if I keep this question open until then.

    You are mixing responsibilities of who starts and stops what.

    Oh, I was so convinced I was doing it right. I think my example was not based in Qt's ones but someone else's I found elsewhere. I will recheck the documentation to understand it better even if I finally don't use Qthread.

    So the conclusion from all this is that if I think I need Qthreads it's because I'm probably doing it wrong and not going the Qt way (like using requests instead of QNetworkAccessManager), right?


  • Lifetime Qt Champion

    Threading has its use, but there's a fair amount of time where it's the wrong choice.

    Qt does not forbid use of threads but most of the time you do not need them.

    In your case you could still use requests which is a very great module using QtConcurrent::run which would remove the need for you to handle the threading yourself.



  • Oh, I will add QtConcurrent to my to-read list, then. It seems there is still a lot I need to learn about Qt!

    But I noticed I still have two more cases where I use threading that wouldn't be solved by QNetworkAccessManager because they are not related to network request (or not totally).

    In the first case, I compare two QListWidgets to match the items that are repeated in both lists:

        def compare_lists(self):
                lList = self.findChild(QListWidget, "leftList")
                rList = self.findChild(QListWidget, "rightList")
        
                lPos = 0
                rPos = 0
                while lPos < lList.count() or rPos < rList.count():
                    # This is signal connected to a slot that updates a QStatusBar
                    self.status.emit("Comparing lists ({} % completed).".format(round((lPos + rPos) * 100 / (lList.count() + rList.count()))))
        
                    if (lPos < rPos and lPos < lList.count()) or ((lPos >= rPos) and (rPos >= rList.count())):
                        thisList = lList
                        otherList = rList
                        pos = lPos
                        item = lList.item(pos)
                        lPos += 1
                    else:
                        thisList = rList
                        otherList = lList
                        pos = rPos
                        item = rList.item(pos)
                        rPos += 1
        
                    if "peer" in item.data and item.data["peer"]:
                        continue
        
                    for i in range(pos, otherList.count()):
                        otherItem = otherList.item(i)
                        if item.text() == otherItem.text():
                            item.data["peer"] = i
                            otherItem.data["peer"] = pos
                            break
    
    threading.Thread(target=compare_lists).start()
    

    In the second case, I launch a network request for every item that is found in only one of the lists:

        def submit_requests(self):
            lList = self.findChild(QListWidget, "leftList")
            rList = self.findChild(QListWidget, "rightList")
            lPos = 0
            rPos = 0
    
            while lPos < lList.count() or rPos < rList.count():
                # The same signal connected to the same slot that updates QStatusBar
                self.status.emit("Sending requests ({} % completed).".format(round((lPos + rPos) * 100 / (lList.count() + rList.count()))))
    
                if (lPos < rPos and lPos < lList.count()) or ((lPos >= rPos) and (rPos >= rList.count())):
                    item = lList.item(lPos)
                    lPos += 1
                else:
                    item = rList.item(rPos)
                    rPos += 1
    
                if "peer" in item.data and item.data["peer"] is not None:
                    continue
    
                request = self.request(item.data)
    
    threading.Thread(target=self.submit_requests).start()
    

    I guess the first case is where QtConcurrent may be of use and I think there would be different ways to implement the second case either with QtConcurrent or QNetworkAccessManager.

    Also, on a slightly different topic, is there any difference or additional complexity on using Python? Should I consider droppping the whole Python and switch to C++ as this is Qt's native language?

    The only reason I chose Python is because it's very straightforward to run the same application in different platforms (i.e., very easily portable) and it's usually easier and quicker to code and test — until you find some issue that takes you days to solve like this one.


  • Lifetime Qt Champion

    You should not access GUI elements from other threads than the main thread.


Log in to reply