Let's say I have a PyQt GUI application and a QTimer
running in the background using a QThread
. The QTimer
is set to an interval of 1 second and is running infinitely.
Now the main thread gets busy with some external C library call and blocks for 1 minute. Will the QTimer
continue to get triggered during that period and signal it?
I am in a conceptual stage so there is no code to show yet. I know it would make more sense to do the big call in a background thread itself.
In short words, yes, QTimer will update on the other thread, granted that you take care of the thread's lifespan yourself and quit it only at the most appropriate times. Also be careful about the QTimer's
reference, so it is not collected by the python's garbage collector (you'll need to stop the QTimer
at one point).
Now, with a bit of code to illustrate a more practical example, and so you can test it yourself:
from PySide2.QtWidgets import QApplication, QMainWindow
from PySide2.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
from PySide2.QtCore import QObject, QThread, QTimer, Signal
class TimerWorker(QObject):
timeout = Signal(int)
def __init__(self):
QObject.__init__(self)
self.count = 0
def run(self):
self.timer = QTimer()
self.timer.setInterval(250)
self.timer.timeout.connect(self.process)
self.timer.start()
def finished(self):
self.timer.stop()
def process(self):
self.count += 1
self.timeout.emit(self.count)
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# Widgets
self.scene = QWidget()
self.label = QLabel("Count: 0")
self.busyBt = QPushButton("Sleep")
self.busyBt.clicked.connect(self.becomeBusy)
# Scene
layout = QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.busyBt)
self.scene.setLayout(layout)
self.setCentralWidget(self.scene)
# Threads
self.thread = QThread()
self.worker = TimerWorker()
self.worker.moveToThread(self.thread)
self.worker.timeout.connect(self.processCount)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.worker.finished)
self.thread.start()
def becomeBusy(self):
timeout = 5
print('Main Thread: Sleeping for %d seconds...' % (timeout))
QThread.sleep(timeout)
print('Main Thread: Awaken')
def processCount(self, count):
self.label.setText("Count: %d" % (count))
def closeEvent(self, evt):
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QApplication()
win = Window()
win.show()
app.exec_()
In this example, we start a QThread
which executes TimerWorker.run()
on the other thread (using the moveToThread method). At each 0.25 seconds, it emits a signal that is scheduled on the main thread, to update the QLabel
text.
At the same time, the user can click a QPushButton
to stop the main thread for 5 seconds. During that time, the application will freeze.
By executing the script and clicking on the QPushButton
to freeze the main thread, the QTimer
keeps running and emitting the signal from the other thread. But as the main thread is sleeping (or busy), the signal is never processed. The signal will only be processed after the main thread awakes.
So, if you need to keep the GUI always responsive for the Client (and always processing emitted signals), its recomended that you do all the heavy work on the QThread
side, to not keep the application frozen for a time.