I have a GUI witch i need to update constantly using a Qtimer, for that I use a worker Qthread, this is my code :
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtCore import QThread, QTimer
import sys
import threading
class WorkerThread(QThread):
def run(self):
print("thread started from :" + str(threading.get_ident()))
timer = QTimer(self)
timer.timeout.connect(self.work)
timer.start(5000)
self.exec_()
def work(self):
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
the output is:
Starting worker from :824
thread started from :5916
working from :824
working from :824
the timer is working on the main thread witch freeze my Gui, How can i fix that ?
Answer: in your case I do not see the need to use QThread
.
When do I need to use another thread in the context of a GUI?
Only one thread should be used when some task can block the main thread called the GUI thread, and the blocking is caused because the task is time-consuming, preventing the GUI eventloop from doing its job normally. All modern GUIs are executed in an eventloop that is allows you to receive notifications from the OS like the keyboard, the mouse, etc. and also allows you to modify the status of the GUI depending on the user.
In your case I do not see any heavy task, so I do not see the need for a QThread, I do not really know what the task is that you want to run periodically.
Assuming you have a task that consumes a lot of time, say 30 seconds and you have to do it every half hour, then the thread is necessary. and in your case you want to use a QTimer for it.
Let's go in parts, QTimer
is a class that inherits from a QObject
, and a QObject
belongs to the same as the parent, and if it does not have a parent it belongs to the thread where it was created. On the other hand many times it is thought that a QThread
is a thread of Qt, but it is not, QThread
is a class that allows to handle the life cycle of a native thread, and that is clearly stated in the docs: The QThread class provides a platform-independent way to manage threads.
Knowing the above, let's analyze your code:
timer = QTimer(self)
In the above code self is the parent of QTimer and self is the QThread
, so QTimer
belongs to the thread of the parent of QThread
or where QThread was created, not to the thread that QThread
handles.
Then let's see the code where QThread
was created:
worker = WorkerThread()
As we see QThread
has no parent, then QThread
belongs to the thread where it was created, that is, QThread
belongs to the main thread, and consequently its QTimer
child also belongs to the main thread. Also note that the new thread that QThread
handles only has the scope of the run()
method , if the method is elsewhere belongs to the field where QThread
was created, with all the above we see that the output of the code is correct, and the QThread.sleep(5)
runs on the main thread causing the eventloop to crash and the GUI to freeze.
So the solution is to remove the parent of QTimer
so that the thread it belongs to is the one of the run()
method, and move the work function within the same method. On the other hand it is a bad practice to create static attributes unnecessarily, considering the above the resulting code is the following:
import sys
import threading
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class WorkerThread(QThread):
def run(self):
def work():
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
print("thread started from :" + str(threading.get_ident()))
timer = QTimer()
timer.timeout.connect(work)
timer.start(10000)
self.exec_()
class MyGui(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.worker = WorkerThread(self)
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MyGui()
gui.show()
sys.exit(app.exec_())
Output:
Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168
Observations:
The heavy task that has been emulated is 5 seconds, and that task must be executed every 10 seconds. If your task takes longer than the period you should create other threads.
If your task is to perform a periodic task that is not as heavy as showing time then do not use new threads because you are adding complexity to a simple task, besides this may cause the debugging and testing stage to be more complex.