PDA

View Full Version : QThread does not work as it is supposed to do...



Vincent Le Saux
14th January 2017, 08:55
Dear all,

I would like to use QThread for my app for computational demanding methods. Up to now, I was sublassing QThread and reimplemented the run() method. And it worked actually. Last week-end, I read several blogs, including the one of one Qt developer, that say that this is not the way to do things. Instead, it is better to create a worker that does the task and to move that worker to a thread using the moveToThread method. I managed to create a minimal example that worked with that method. I then tried to do the same using a QMainWindow, and it stopped working. I spent some time trying to understand what was wrong with my code, and was not able to find it. May be one of you can help me?

Here is a minimal python example. Both ways to use threads are implemented. If you want to test it with a QThead, please comment line 41 and uncomment line 42. If you want to use the worker method, just do the opposite. The code displays the id of the thread in the terminal and is connected to a label in the statusbar (if added using the 'add widget' entry in the file menu). Using the QThread method, everything works perfectly. The gui remains responsive and the label is updated correctly. Using the worker method, the gui is frozen and the label is updated once the thread has finished its job.

Any help would be highly appreciated and thank you in advance for your time.

If that help, I work on Archlinux using python 3.6 and PyQt 5.7.

Best regards,

Vincent

Here is the code :


from PyQt5 import QtWidgets, QtCore, QtGui
import sys, time

class Worker(QtCore.QObject):
sigStatusChanged = QtCore.pyqtSignal(int)
sigTaskFinished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)

def task(self):
for n in range(10):
time.sleep(0.1)
self.sigStatusChanged.emit(n)
print('thread : ', int(self.thread().currentThreadId()))
self.sigTaskFinished.emit()

class Thread(QtCore.QThread):
sigStatusChanged = QtCore.pyqtSignal(int)
sigTaskFinished = QtCore.pyqtSignal()
def __init__(self, parent):
QtCore.QThread.__init__(self, parent)

def run(self):
for n in range(10):
time.sleep(0.1)
self.sigStatusChanged.emit(n)
print('thread : ', int(self.thread().currentThreadId()))
self.sigTaskFinished.emit()

class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setWindowTitle("Test de QThread")
self.statusBar().showMessage('Welcome', 1000)
self.mdiArea = QtWidgets.QMdiArea()
self.setCentralWidget(self.mdiArea)
self.lbl = None

fileMenu = self.menuBar().addMenu('File')

threadAction = QtWidgets.QAction('Thread', self)
#threadAction.triggered.connect(self.threadWithWor ker)
threadAction.triggered.connect(self.threadWithThre ad)

addWidgetAction = QtWidgets.QAction('Add Widget', self)
addWidgetAction.triggered.connect(self.addWidget)
removeWidgetAction = QtWidgets.QAction('Remove Widget', self)
removeWidgetAction.triggered.connect(self.removeWi dget)

fileMenu.addAction(threadAction)
fileMenu.addAction(addWidgetAction)
fileMenu.addAction(removeWidgetAction)

print('app : ', int(self.thread().currentThreadId()))

def threadWithWorker(self):
def stopThread():
thread.quit()
thread.wait()
thread = QtCore.QThread()
thread.start()
worker = Worker()
worker.moveToThread(thread)
worker.sigTaskFinished.connect(stopThread)
worker.sigStatusChanged.connect(self.updateStatus)
worker.task()

def threadWithThread(self):
thread = Thread(self)
QtCore.QCoreApplication.processEvents()
thread.sigStatusChanged.connect(self.updateStatus)
thread.start()

def updateStatus(self, val):
if self.lbl is not None:
self.lbl.setText(str(val))

def addWidget(self):
self.lbl = QtWidgets.QLabel('Test')
self.statusBar().addWidget(self.lbl)

def removeWidget(self):
if self.lbl is not None:
self.statusBar().removeWidget(self.lbl)

if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
view = MainWindow()
view.show()
app.exec_()

anda_skoa
14th January 2017, 12:22
Up to now, I was sublassing QThread and reimplemented the run() method. And it worked actually.

I would have kept using the solution that is known to work :)



Last week-end, I read several blogs, including the one of one Qt developer, that say that this is not the way to do things. Instead, it is better to create a worker that does the task and to move that worker to a thread using the moveToThread method.

If you ask me that article is complete nonsense.

This approach is nice if your worker task needs an event loop, but just overhead if the task is running a single blocking function.



I managed to create a minimal example that worked with that method. I then tried to do the same using a QMainWindow, and it stopped working.

You are calling "task()" in the context of the main thread.

Cheers,
_

Vincent Le Saux
14th January 2017, 19:05
Hi anda_skoa,

Thank you for your reactivity. It is highly appreciated. I have two remarks after reading your response.

1. Could you be more precise about the 'non sense' nature of the article? I do not know that much, so any tip/comment is welcomed ;)

2. I do not understand your last remark about the "task()" method. Is it the reason why it is not working? If yes, why? And what would the fix look like?

3. (I know, I said three) One advantage, according to what I read, is that it is easier to stop a thread using the worker approach (do not ask me why, I don't know :p). How can I stop a thread using the QThread approach (the one that works)? Just calling thread.quit()?

Thank you very much.

Regards,

Vincent

anda_skoa
15th January 2017, 11:48
1. Could you be more precise about the 'non sense' nature of the article? I do not know that much, so any tip/comment is welcomed ;)


It claims that the worker object approach is the best approach for all cases, which it is not.

The approach to reimplement run() is fine, even better, for certain type of paralllel tasks, e.g. single long running functions, single continuously running functions, etc.

The worker object approach is nicer for event processsing tasks, but unnecessarily complex for tasks that don't need that.



2. I do not understand your last remark about the "task()" method. Is it the reason why it is not working? If yes, why? And what would the fix look like?

In your threadWithWorker method you create a thread, start it and then just let is sit idly in its event loop.
Then you create a worker object and execute its task() method in the main thread.



3. (I know, I said three) One advantage, according to what I read, is that it is easier to stop a thread using the worker approach (do not ask me why, I don't know :p).

See, that's why the article it nonsense.
That assumes the worker task is event loop driven, so simply shutting down the thread's event loop would end the thread and the processing.

For a non-event driven task, like in your case, it is more complicated because you need to end the task and the thread.



How can I stop a thread using the QThread approach (the one that works)? Just calling thread.quit()?

You don't need quit() because the thread is not running its event loop.
You need to stop the task, e.g. exit the loop.
The thread ends once run() returns.

Cheers,
_

Vincent Le Saux
15th January 2017, 16:45
OK, things are clearer for me now. I'll keep using the QThread approach (the one that works ;) ).

Thank you very much of your time!

Regards,

Vincent