Search code examples
pythonpython-multiprocessingpython-multithreadingpyside6

Python pyqt6 window blocks main loop


I have a small program that does something, but i want to "switch modes", for now i press a key and an input prompts on the console, but to make it easier i want to make a window with pyqt6, the problem is that the window blocks or halts the main loop while it's open, i tried with threading/multiprocessing but i can't make it work.

import threading
from queue import Queue

from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt

queue = Queue()

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        layout = QVBoxLayout()
        label = QLabel("Change modes")
        btn1 = QPushButton("MODE 1")
        btn2 = QPushButton("MODE 2")
        layout.addWidget(label)
        layout.addWidget(btn1)
        layout.addWidget(btn2)    
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        btn1.clicked.connect(self.mode1)
        btn2.clicked.connect(self.mode2)
        self.show()

    def mode1(self):
        queue.put("mode1")

    def mode2(self):
        queue.put("mode2")


if __name__ == '__main__':

    app = QApplication()
    window = MainWindow()
    app.exec()

    mode = "none"

    while True:

        _mode = queue.get()
        if mode != _mode:
            mode = _mode;
            print(f"mode: {mode}")

        # do stuff here

the only way that the while loop executes is when i close the window.


Solution

  • Traditional Python multiprocessing/multithreading libraries such as multiprocessing and threading do not work well with Qt-like (PyQt and PySide) graphical programs. Fortunately, among other solutions, PySide provides the QThread interface, allowing multithreading in PySide graphical interfaces. It can be applied to your program as follows:

    import threading
    from queue import Queue
    
    from PySide6.QtWidgets import *
    from PySide6.QtGui import *
    from PySide6.QtCore import Qt, QThread
    
    queue = Queue()
    
    class Worker(QThread):
        def __init__(self):
            super(Worker, self).__init__()
    
        def run(self):
            mode = "none"
    
            while True:
    
                _mode = queue.get()
                if mode != _mode:
                    mode = _mode;
                    print(f"mode: {mode}")
    
                # do stuff here
    
    
    class MainWindow(QMainWindow):
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            layout = QVBoxLayout()
            label = QLabel("Change modes")
            btn1 = QPushButton("MODE 1")
            btn2 = QPushButton("MODE 2")
            layout.addWidget(label)
            layout.addWidget(btn1)
            layout.addWidget(btn2)
            widget = QWidget()
            widget.setLayout(layout)
            self.setCentralWidget(widget)
            btn1.clicked.connect(self.mode1)
            btn2.clicked.connect(self.mode2)
            self.show()
    
            self.worker = Worker()  # Create a Worker instance
            self.worker.start()  # Start the Worker instance (which calls the run function of the Worker instance)
    
        def mode1(self):
            queue.put("mode1")
    
        def mode2(self):
            queue.put("mode2")
    
        def closeEvent(self, event):
            self.worker.terminate()  # When the window closes, stop the thread
    
    if __name__ == '__main__':
    
        app = QApplication()
        window = MainWindow()
        app.exec()
    
    

    Please note the changed import statement of PySide6.QtCore (to import QThread), the addition of the self.worker variable in the __init__ function of the MainWindow class (to actually start the thread), as well as the addition of a closeEvent function in the MainWindow class (to terminate the thread when the window closes).