For my project based on Python and Qt I wanted to move expensive calculations and functions providing server/client functions into separate threads, to unfreeze my GUI. While leaving them running, I still want them to check periodically if there is new data from the main thread. For testing, I therefore implemented the following demo code:
import sys
from time import sleep
import shiboken6
from PySide6.QtCore import Qt, QObject, QThread, Signal, Slot, QTimer
from PySide6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
class Worker(QObject):
finished = Signal()
progress = Signal(int)
def __init__(self):
super().__init__()
self.print_to_console_plz = False
@Slot()
def print_on_console_while_running(self):
self.print_to_console_plz = True
print("Set print_to_console to true")
def run(self):
timer = QTimer()
for i in range(5):
sleep(0.9)
timer.start(100)
if self.print_to_console_plz:
print("Hello World from worker")
self.print_to_console_plz = False
self.progress.emit(i + 1)
self.finished.emit()
class Window(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.clicksCount = 0
self.initWorker()
self.setupUi()
def initWorker(self):
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.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
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.clicksToConsoleLabel = QLabel("Click here to print to console", self)
self.clicksToConsoleLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.countBtn = QPushButton("Click me!", self)
self.countBtn.clicked.connect(self.countClicks)
self.ToConsoleBttn = QPushButton("Print to console!", self)
self.ToConsoleBttn.clicked.connect(self.worker.print_on_console_while_running)
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.clicksToConsoleLabel)
layout.addWidget(self.ToConsoleBttn)
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):
"""Long-running task in 5 steps."""
# Step 6: Start the thread
if not shiboken6.isValid(self.thread):
self.initWorker()
self.ToConsoleBttn.clicked.connect(self.worker.print_on_console_while_running)
self.thread.start()
# Final resets
self.longRunningBtn.setEnabled(False)
self.thread.finished.connect(
lambda: self.longRunningBtn.setEnabled(True)
)
self.thread.finished.connect(
lambda: self.stepLabel.setText("Long-Running Step: 0")
)
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
My main aim was to let the "expensive" function run in the worker thread while counting up, but it should still check periodically if there is new data available (represented by a call to print_on_console_while_running
). To avoid having the run-function blocking everything while it is executed, I also introduced a QTimer
as non-blocking timer.
Still, regardless, whenever I press on the button with "Print to console!" while the worker is executing the run-function, I always get "Set print_to_console to true" printed after the run
-function has finished, and not during execution, indicating that the run-function still blocks the execution of everything else.
What am I doing wrong here, and how can I send data from the main thread to the worker thread while still executing the run-function?