Block the Qt GUI Thread or how not to do things

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import os
import sys

from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtWidgets import (QApplication, 
    QWidget, QPushButton, QLabel, QVBoxLayout)


class Task(QObject):
    
    progress = Signal(str)
    
    def __init__(self, parent=None):
        super().__init__(parent)
    
    # 1. Create a long running task
    #    We search for a file using os.walk()
    #    using a tight (blocking) for loop.
    #    The loop blocks the Qt event loop
    #    so no signals are send or events
    #    processed until the loop exits.
    #    This effectively freezes the Gui.
    
    @Slot()
    def do_work(self):
        path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
        name = 'bogus'
        for root, _, files in os.walk(path):
            self.progress.emit(root)
            if name in files:
                print(os.path.join(root, name))


class Window(QWidget):
    
    def __init__(self):

        super().__init__()
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        # 2. Create a push button
        
        self.button = QPushButton('Start working')
        self.label = QLabel()
        
        self.button.clicked.connect(self.do_work)
        
        layout.addWidget(self.button)
        layout.addWidget(self.label)
    
    # 3. Execute the long running task.
    
    def do_work(self):
        self.task = Task()
        self.task.progress.connect(self.label.setText)
        self.task.do_work()
        

if __name__ == '__main__':

    app = QApplication(sys.argv)

    main_window = Window()
    main_window.show()

    sys.exit(app.exec())


Threads of execution let you execute your code concurrently sharing the program memory and other resources and are represented in Qt by the QThread class. There are two use cases for threads:

  • Make processing faster by using more than one processor core,
  • Keep the Gui thread responsive by moving long running tasks to other threads

We’ll focus on the second one but first let’s see what a nonresponsive Qt Gui looks like. To demonstrate a long lasting task that blocks the Gui

  1. Create the task. In the example we use os.walk() and a blocking (“tight”) for loop to search the file system for a bogus file. We also try to report progress after each file using a custom Qt signal. This does not work because the loop blocks the Qt event loop and no signals are sent or events processed until the loop exits. The loop is executed in the main application thread (also called the Gui thread) so the whole application becomes nonresponsive - it freezes and you can’t even close it.

  2. Create a push button,

  3. Create a slot that starts the long running task when the push button is clicked.