In this simulated example I am repeating a task every 1 second using QTimer by implementing a QObject, then move it to a QThead. The simulated task takes 0.5 second. The task runs in a new thread, which should not have interfered with the main thread in which the GUI is running. However, the task will freeze the GUI repetitively.
import sys
from time import sleep
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
class Worker(QObject):
progress = pyqtSignal(int)
def __init__(self):
super().__init__()
self.i=0
self.Timer=QTimer(self)
self.Timer.timeout.connect(self.run)
self.Timer.setInterval(1000)
def run(self):
"""Long-running task."""
sleep(0.5)
self.i=self.i+1
self.progress.emit(self.i)
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.setupUi()
def setupUi(self):
self.setWindowTitle("Freezing GUI")
self.resize(300, 150)
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
# Create and connect widgets
self.clicksLabel = QLabel("Counting: 0 clicks", self)
self.clicksLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.stepLabel = QLabel("Long-Running Step: 0")
self.stepLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton("Click me!", self)
self.countBtn.clicked.connect(self.countClicks)
self.longRunningBtn = QPushButton("Long-Running Task!", self)
self.longRunningBtn.clicked.connect(self.runLongTask)
# Set the layout
layout = QVBoxLayout()
layout.addWidget(self.clicksLabel)
layout.addWidget(self.countBtn)
layout.addStretch()
layout.addWidget(self.stepLabel)
layout.addWidget(self.longRunningBtn)
self.centralWidget.setLayout(layout)
def countClicks(self):
self.clicksCount += 1
self.clicksLabel.setText(f"Counting: {self.clicksCount} clicks")
def reportProgress(self, n):
self.stepLabel.setText(f"Long-Running Step: {n}")
def runLongTask(self):
# Step 2: Create a QThread object
self.thread = QThread()
# Step 3: Create a worker object
self.worker = Worker()
# Step 4: Move worker to the thread
self.worker.moveToThread(self.thread)
# Step 5: Connect signals and slots
self.thread.started.connect(self.worker.Timer.start)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
# Step 6: Start the thread
self.thread.start()
self.longRunningBtn.setEnabled(False)
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
If I reduce the work in the task by removing sleep() or setting a smaller time for the sleep() function in Worker.run(), then the GUI will not freeze. However, realistically I am doing a task that requires a long time to complete, which makes it a problem.
Thanks for the ideas and inputs from the comments. It turns out that the problem comes from the fact that
runis a member function of theWorkerclass, but is not a Signal or Slot of theWorkerclass. Moving the worker to the new thread only affect its Signals and Slots, notrun. Sorunis still executed in the main thread.One solution will be to use the
@pyqtSlot()decorator when definingrun, to make it aSlotof the Worker class. Also, specifyingDirectionConnectionbetweenTimer.timeoutandWorker.runcan also force therunfunction to run in the newly created thread thatTimerhas been moved to.