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 thoseQThread: 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 andMainWindow.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 whenMainWindow.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 thecount()
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?
-
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.
-
@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.
-
-
Hi, thank you both for your tips.
I didn't know
QNetworkAccessManager
, that's why I was going the Python way withrequests
+threading
because I didn't know that other more Qtish way of doing it withQNetworkAccessManager
I will have a look at rewriting my code with
QNetworkAccessManager
, which will be probably more convenient than usingrequests
+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 ofQNetworkAccessManager
), right? -
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 byQNetworkAccessManager
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 withQtConcurrent
orQNetworkAccessManager
.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.
-
You should not access GUI elements from other threads than the main thread.