27. Multithreading - moveToThread

Threads of execution let you execute your code concurrently while sharing the program memory and other resources. There are primary two use cases for threads:

  • Accelerate processing by utilizing multiple processor cores,
  • Maintain GUI responsiveness by offoading long-running tasks to background threads.

In the following examples, we focus on the second use case. First, however,let’s demonstrate the issue by showing a non-responsive Qt GUI.

27.1 Blocking the Qt GUI: How Not to Do It

An icon of a clipboard-list1

You need to search the filesystem for a file and report progress.

 1 import os
 2 import sys
 3 from PySide6.QtCore import QObject, Signal, Slot
 4 from PySide6.QtWidgets import (QApplication, 
 5     QWidget, QPushButton, QLabel, QVBoxLayout)
 6 
 7 
 8 class Task(QObject):
 9     
10     progress = Signal(str)
11     
12     def __init__(self, parent=None):
13         super().__init__(parent)
14     
15     # 1. Create a long running task
16     #    We search for a file using os.walk()
17     #    using a tight (blocking) for loop.
18     #    The loop blocks the Qt event loop
19     #    so no signals are send or events
20     #    processed until the loop exits.
21     #    This effectively freezes the Gui.
22     
23     @Slot()
24     def do_work(self):
25         path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
26         name = 'bogus'
27         for root, _, files in os.walk(path):
28             self.progress.emit(root)
29             if name in files:
30                 print(os.path.join(root, name))
31 
32 
33 class Window(QWidget):
34     
35     def __init__(self):
36 
37         super().__init__()
38         
39         layout = QVBoxLayout()
40         self.setLayout(layout)
41         
42         # 2. Create a push button
43         
44         self.button = QPushButton('Start working')
45         self.label = QLabel()
46         
47         self.button.clicked.connect(self.do_work)
48         
49         layout.addWidget(self.button)
50         layout.addWidget(self.label)
51     
52     # 3. Execute the long running task.
53     
54     def do_work(self):
55         self.task = Task()
56         self.task.progress.connect(self.label.setText)
57         self.task.do_work()
58         
59 
60 if __name__ == '__main__':
61 
62     app = QApplication(sys.argv)
63     main_window = Window()
64     main_window.show()
65     sys.exit(app.exec())
  1. Create the task. In the example, we use os.walk() within a for loop to search the file system for a non-existent file. We attempt to report progress after each iteration using a custom Qt signal named progress. This fails because the loop blocks the Qt event loop, preventing the signals from being emitted or events from being processed until the loop completes. Since the loop runs in the main application thread (also known as the GUI thread), the entire application becomes unresponsive - it freezes and you cannot even close it.

  2. Create a push button.

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

27.2 A Minimal Working Example

The previous example illustrates how a PySide6 GUI can become unresponsive. Now let’s explore using Qt threads to run long tasks in the background while keeping the GUI responsive.

An icon of a clipboard-list1

You need to create a minimal working Qt multithreading application.

 1 from PySide6.QtCore import QObject, Signal, Slot
 2 
 3 # 1. Create the worker_obj class
 4 
 5 class Worker(QObject):
 6     
 7     finished = Signal()
 8     error = Signal(str)
 9     
10     def __init__(self, parent=None):
11         super().__init__(parent)
12     
13     # This method to be executed
14         
15     @Slot()
16     def process(self):
17         print('Hello World')
18         self.finished.emit()
  1. Create a QObject subclass containing the slot/method to execute in a background thread (i.e., a thread other than the GUI thread). In the example, the slot is named process(); it prints a message and emits a custom signal named finished() before returning. The Worker class declares an error() signal, which could be emitted under error conditions during process() execution.
 1 # https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
 2 
 3 import sys
 4 from PySide6.QtCore import QThread, Slot, Qt
 5 from PySide6.QtWidgets import (QApplication,
 6     QPushButton, QLabel, QWidget, QVBoxLayout)
 7 from worker import Worker
 8 
 9 
10 class Window(QWidget):
11     
12     def __init__(self):
13 
14         super().__init__()
15         
16         layout = QVBoxLayout()
17         self.setLayout(layout)
18         
19         button = QPushButton('Start background thread')
20         button.clicked.connect(self.on_button_clicked)
21         
22         self.label = QLabel()
23         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
24         
25         layout.addWidget(button)
26         layout.addWidget(self.label)
27     
28     @Slot()
29     def on_button_clicked(self):
30         
31         # 2. Create the thread object
32         
33         self.background_thread = QThread()
34         
35         # 3. Create the worker_obj and move it to the thread
36         
37         self.worker_obj = Worker()
38         self.worker_obj.moveToThread(self.background_thread)
39         
40         self.worker_obj.finished.connect(self.on_finished)
41         
42         # 4. Connect the signals and the slots 
43         
44         self.worker_obj.error.connect(self.on_error)
45         self.background_thread.started.connect(self.worker_obj.process)
46         self.worker_obj.finished.connect(self.background_thread.quit)
47         self.worker_obj.finished.connect(self.worker_obj.deleteLater)
48         self.background_thread.finished.connect(self.background_thread.deleteLater)
49         
50         # 5. Start the thread
51         
52         self.background_thread.start()
53     
54     @Slot()
55     def on_finished(self):
56         self.label.setText('Worker finished')
57     
58     @Slot()
59     def on_error(self, message):
60         print(message)
61 
62 
63 if __name__ == '__main__':
64 
65     app = QApplication(sys.argv)
66 
67     main_window = Window()
68     main_window.show()
69 
70     sys.exit(app.exec())

Then, in the main window class:

  1. Create a QThread object named background_thread (avoid naming it thread, as that name is already used). Use self to make it the member of the main window, ensuring it remains in scope while running.

  2. Create a Worker object and move it to background_thread using QObject.moveToThread(). Now, Worker.process() will execute in background_thread.

  3. Connect the appropriate signals and slots:

    • Connect background_thread.started() to worker.process(). This executes Worker.process() when the background thread starts.
    • Connect Worker.finished() to background_thread.quit(). This quits the background thread when Worker.process() returns.
    • Connect Worker.finished() to Worker.deleteLater() method. This deletes the worker object some time after it emits finished().
    • Connect background_thread.finished() to background_thread.deleteLater(). This deletes the background thread some time after it finishes.
  4. Start background_thread using QThread.start().

With these steps, process() runs upon thread start, the thread quits when the method returns, and both the worker and thread object are destroyed - all while keeping the GUI responsive.

27.3 Walking the Filesystem

The moveToThread() template provides a basic structure, but the worker does nothing but print a message. For a more practical example, let’s have Worker.process() traverse the filesystem using os.walk() from the root. This operation can be time-consuming and would block the GUI if run on the main thread.

An icon of a clipboard-list1

You need to use Qt multithreading to search the filesystem for a file and report progress.

 1 import os
 2 from PySide6.QtCore import QObject, QThread, Signal, Slot
 3 
 4 # 1. Create the worker_obj class
 5 
 6 class Worker(QObject):
 7     
 8     finished = Signal()
 9     progress = Signal(str)
10     error = Signal(str)
11     
12     def __init__(self, parent=None):
13         super().__init__(parent)
14     
15     # This is the method we want to execute.
16     # We are in a tight loop.
17 
18     @Slot()
19     def process(self):
20         path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
21         for root, _, _ in os.walk(path):
22             if QThread.currentThread().isInterruptionRequested():
23                 return
24             self.progress.emit(os.path.basename(root))
25         self.finished.emit()
  1. Create the worker class. The method, process() uses Python’s os.walk() to traverse the filesystem. For each enumerated filesystem object, we emit a custom progress signal (added to Worker alongside finished and error). If the background thread receives an interruption request, we return from process(), triggering the signal-slot chain to stop and delete both the worker and the background thread. Note how in Worker.process(), we access the current (background) thread via the static QThread.currentThread() method.
  1 import sys
  2 from PySide6.QtCore import QThread, Slot, Qt
  3 from PySide6.QtWidgets import (QApplication, QPushButton,
  4     QLabel, QWidget, QVBoxLayout)
  5 from worker import Worker
  6 
  7 class Window(QWidget):
  8     
  9     def __init__(self):
 10 
 11         super().__init__()
 12         
 13         layout = QVBoxLayout()
 14         self.setLayout(layout)
 15         
 16         self.start_button = QPushButton('Start background thread')
 17         self.start_button.clicked.connect(self.on_start_button_clicked)
 18         
 19         self.cancel_button = QPushButton('Cancel')
 20         self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
 21         self.cancel_button.setDisabled(True)
 22         
 23         self.label = QLabel()
 24         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
 25         
 26         layout.addWidget(self.start_button)
 27         layout.addWidget(self.cancel_button)
 28         layout.addWidget(self.label)
 29     
 30     @Slot()
 31     def on_start_button_clicked(self):
 32         
 33         self.start_button.setDisabled(True)
 34         self.cancel_button.setEnabled(True)
 35         
 36         # 2. Create the thread
 37         
 38         self.background_thread = QThread()
 39         
 40         # 3. Create the worker_obj and move it to the thread
 41         
 42         self.worker_obj = Worker()
 43         self.worker_obj.moveToThread(self.background_thread)
 44         
 45         self.worker_obj.finished.connect(self.on_finished)
 46         
 47         # 4. Connect the appropriate signals to ensure
 48         #    both the worker_obj and the thread are destroyed.
 49         
 50         self.worker_obj.error.connect(self.on_error)
 51         self.background_thread.started.connect(self.worker_obj.process)
 52         self.worker_obj.finished.connect(self.background_thread.quit)
 53         self.worker_obj.finished.connect(self.worker_obj.deleteLater)
 54         self.background_thread.finished.connect(self.background_thread.deleteLater)
 55         
 56         self.worker_obj.progress.connect(self.label.setText)
 57         
 58         # 5. Start the thread.
 59         
 60         self.background_thread.start()
 61     
 62     # Stop the work by requesting interruption
 63         
 64     @Slot()
 65     def on_cancel_button_clicked(self):
 66         
 67         self.start_button.setEnabled(True)
 68         self.cancel_button.setDisabled(True)
 69         
 70         if hasattr(self, 'background_thread'):
 71             self.background_thread.requestInterruption()
 72             self.background_thread.quit()
 73             self.background_thread.wait()
 74     
 75     @Slot()
 76     def on_finished(self):
 77         self.label.setText('Worker finished')
 78     
 79     @Slot()
 80     def on_error(self, message):
 81         print(message)
 82     
 83     # Make sure the thread is destroyed
 84     # when the main window is closed.
 85     
 86     def closeEvent(self, event):        
 87         try:
 88             self.background_thread.requestInterruption()
 89             self.background_thread.quit()
 90             self.background_thread.wait()
 91         except Exception as e:
 92             print(e) 
 93 
 94 
 95 if __name__ == '__main__':
 96 
 97     app = QApplication(sys.argv)
 98 
 99     main_window = Window()
100     main_window.show()
101 
102     sys.exit(app.exec())
  1. In the main window class, create the thread object.

  2. Create a Worker object and move it to the QThread using QObject.moveToThread().

  3. Use signals and slots to ensure proper creation and deletion: Starting the background thread triggers Worker.process(); QObject.finished() triggers both QThread.quit() and Worker.deleteLater(); QThread.finished() triggers QThread.deleteLater().

  4. Start the background thread. This occurs in Window.on_start_button_clicked() creating new QThread and Worker objects each time the start button is clicked.

We override QWidget.closeEvent() to interrupt the background thread, ensuring cleanup if the main window closes while the thread runs. The sequence, QThread.requestInterruption() + QThread.quit() + QThread.wait(), is also used in Window.on_cancel_button_clicked() for clean interruption.

27.4 Reusing the QThread object

In the prior examples a new background thread is created each time the task runs. However, the official QThread documentation example creates both thread and worker in the main class constructor. Let’s try to replicate that.

An icon of a clipboard-list1

Your task is to recreate the official Qt multithreading example in PySide6.

 1 from PySide6.QtCore import QObject, Signal, Slot
 2 
 3 # 1. Create the worker_obj class
 4 
 5 class Worker(QObject):
 6     
 7     result_ready = Signal(str)
 8     
 9     def __init__(self, parent=None):
10         super().__init__(parent)
11         
12     @Slot()
13     def do_work(self, parameter):
14         print(parameter)
15         self.result_ready.emit(parameter)
  1. Create the worker class. The background method is do_work(), matching the QThread documentation.
 1 # https://doc.qt.io/qt-6/qthread.html
 2 
 3 import sys
 4 from PySide6.QtCore import QThread, Slot, Signal, Qt
 5 from PySide6.QtWidgets import (QApplication,
 6     QPushButton, QLabel, QWidget, QVBoxLayout)
 7 from worker import Worker
 8 
 9 
10 class Controller(QWidget):
11     
12     operate = Signal(str)
13     
14     def __init__(self):
15 
16         super().__init__()
17         
18         layout = QVBoxLayout()
19         self.setLayout(layout)
20         
21         button = QPushButton('Start background thread')
22         button.clicked.connect(self.on_button_clicked)
23         
24         self.label = QLabel()
25         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
26         
27         layout.addWidget(button)
28         layout.addWidget(self.label)
29         
30         # 2. Create the thread object
31         
32         self.worker_thread = QThread()
33         
34         # 3. Create the worker_obj and move it to the thread
35         
36         self.worker_obj = Worker()
37         self.worker_obj.moveToThread(self.worker_thread)
38         
39         # 4. Connect the signals and the slots
40         
41         self.worker_thread.finished.connect(
42             self.worker_obj.deleteLater)
43         self.operate.connect(self.worker_obj.do_work)
44         self.worker_obj.result_ready.connect(self.handle_results)
45         
46         # 5. Start the thread
47 
48         self.worker_thread.start()
49     
50     # 6. On the button click emit the operate signal
51     
52     @Slot()
53     def on_button_clicked(self):
54         
55         self.operate.emit('Hello World')
56     
57     @Slot()
58     def handle_results(self):
59         self.label.setText('Worker finished')
60     
61     # 7. Quit the thread when the main window is closed
62     
63     def closeEvent(self, event):        
64         try:
65             self.worker_thread.quit()
66             self.worker_thread.wait()
67         except Exception as e:
68             print(e) 
69         event.accept()
70 
71 
72 if __name__ == '__main__':
73 
74     app = QApplication(sys.argv)
75 
76     main_window = Controller()
77     main_window.show()
78 
79     sys.exit(app.exec())

Then, in the main window class:

  1. Create the worker thread.

  2. Create the Worker object and move it to the the worker thread using QObject.moveToThread().

  3. Connect signals and slots: QThread.finished() to QObject.deleteLater() for worker deletion on thread finish; Controller.operate() to Worker.do_work() to start work via custom signal; Worker.result_ready() to Controller.handle_result() for handling results.

  4. Start the worker thread. Steps 2-5 occur in Controller.__init__(), so the thread and the worker persist until the main window closes or explicit deletion.

  5. On button click, emit the operate() signal to execute Worker.do_work().

  6. Override QWidget.closeEvent() to quit the thread using QThread.quit() and QThread.wait().

27.5 Walking the Filesystem reusing the QThread Object

An icon of a clipboard-list1

You need to use Qt multithreading to walk the filesistem but you reuse the same worker thread.

 1 import os
 2 from PySide6.QtCore import (QObject, QMutex, 
 3     QMutexLocker, Signal, Slot)
 4 
 5 # 1. Create the worker_obj class
 6 
 7 class Worker(QObject):
 8     
 9     result_ready = Signal()
10     progress = Signal(str)
11     
12     def __init__(self, parent=None):
13         super().__init__(parent)
14         self.interruption_requested = False
15         self.mutex = QMutex()
16         
17     @Slot()
18     def do_work(self):
19         
20         self.interruption_requested = False
21         
22         path = os.path.abspath('.').split(os.path.sep)[0] + os.path.sep
23         for root, _, _ in os.walk(path):
24             with QMutexLocker(self.mutex):
25                 if self.interruption_requested:
26                     self.progress.emit('Canceled')
27                     self.result_ready.emit()
28                     return
29             self.progress.emit(os.path.basename(root))
30         self.result_ready.emit()
31         
32     @Slot()
33     def stop(self):
34         with QMutexLocker(self.mutex):
35             self.interruption_requested = True
36         
37     @Slot()
38     def reset(self):
39         with QMutexLocker(self.mutex):
40             self.interruption_requested = False
  1. Create the worker class. There are several differences from the first walk filesystem example: We use a boolean flag Worker.interruption_requested instead of QThread.isInterruptionRequested(); add Worker.stop() and Worker.reset() to toggle the flag; guard each use with QMutexLOcker and QMutex for thread safety.
  1 # https://doc.qt.io/qt-6/qthread.html
  2 
  3 import sys
  4 
  5 from PySide6.QtCore import QThread, Slot, Signal, Qt
  6 from PySide6.QtWidgets import (QApplication,
  7     QPushButton, QLabel, QWidget, QVBoxLayout)
  8 from worker import Worker
  9 
 10 
 11 class Controller(QWidget):
 12     
 13     operate = Signal()
 14     
 15     def __init__(self):
 16 
 17         super().__init__()
 18         
 19         layout = QVBoxLayout()
 20         self.setLayout(layout)
 21         
 22         self.start_button = QPushButton('Start background thread')
 23         self.start_button.clicked.connect(self.on_start_button_clicked)
 24         
 25         self.cancel_button = QPushButton('Cancel')
 26         self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
 27         self.cancel_button.setDisabled(True)
 28         
 29         self.label = QLabel()
 30         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
 31         
 32         layout.addWidget(self.start_button)
 33         layout.addWidget(self.cancel_button)
 34         layout.addWidget(self.label)
 35         
 36         # 2. Create the thread
 37         
 38         self.worker_thread = QThread()
 39         
 40         # 3. Create the worker_obj and move it to the thread
 41         
 42         self.worker_obj = Worker()
 43         self.worker_obj.moveToThread(self.worker_thread)
 44         
 45         # 4. Connect the signals with the slots
 46         
 47         self.worker_thread.finished.connect(self.worker_obj.deleteLater)
 48         self.operate.connect(self.worker_obj.do_work)
 49         self.worker_obj.result_ready.connect(self.handle_results)
 50         
 51         self.worker_obj.progress.connect(self.label.setText)
 52         
 53         # 5. Start the thread
 54 
 55         self.worker_thread.start()
 56     
 57     # 6. On the start button click emit the operate signal
 58     
 59     @Slot()
 60     def on_start_button_clicked(self):
 61         
 62         self.start_button.setDisabled(True)
 63         self.cancel_button.setEnabled(True)
 64         
 65         self.worker_obj.reset()
 66         self.operate.emit()
 67     
 68     # 7. On the cancel button click stop the worker_obj
 69     
 70     @Slot()
 71     def on_cancel_button_clicked(self):
 72         
 73         self.start_button.setEnabled(True)
 74         self.cancel_button.setDisabled(True)
 75         self.worker_obj.stop()
 76 
 77     @Slot()
 78     def handle_results(self):
 79         self.label.setText('Worker finished')
 80     
 81     # 8. Quit the thread when the main window is closed
 82     
 83     def closeEvent(self, event):        
 84         try:
 85             self.worker_obj.stop()
 86             self.worker_thread.quit()
 87             self.worker_thread.wait()
 88         except Exception as e:
 89             print(e) 
 90         event.accept()
 91 
 92 
 93 if __name__ == '__main__':
 94 
 95     app = QApplication(sys.argv)
 96 
 97     main_window = Controller()
 98     main_window.show()
 99 
100     sys.exit(app.exec())
  1. In Controller.__init__() create the worker thread object.

  2. Create the worker object and move it to the worker thread using QObject.moveToThread().

  3. Connect signals and slots. Main class operate() signal triggers Worker.do_work().

  4. Start the worker thread.

  5. On start button click, reset the worker with Worker.reset() and emit operate().

  6. On cancel button click, stop the worker with Worker.stop().

  7. Quit the thread on main window close.

But why did we use a mutex-guarded boolean flag? QThread.requestInterruption() is one-time: once requested, QThread.isInterruptionRequested() stays True and it cannot be reset. A signal to toggle a flag would require QApplication.processEvents() in the blocking loop. DIrect flag setting is unsafe across threads, so QMutex and QMutexLocker are used.

27.6 Signals and Slots Across Threads

 1 from PySide6.QtCore import QObject, QThread, Signal, Slot
 2 
 3 class Worker(QObject):
 4     
 5     auto_signal = Signal()
 6     direct_signal = Signal()
 7     queued_signal = Signal()
 8     blocking_signal = Signal()
 9     
10     @Slot()
11     def auto_slot(self):
12         print('Auto connection')
13         print('In', QThread.currentThread().objectName(),
14             ', Loop level', QThread.currentThread().loopLevel())
15 
16     @Slot()
17     def direct_slot(self):
18         print('Direct connection')
19         print('In', QThread.currentThread().objectName(),
20             ', Loop level', QThread.currentThread().loopLevel())
21     @Slot()
22     def queued_slot(self):
23         print('Queued connection')
24         print('In', QThread.currentThread().objectName(),
25             ', Loop level', QThread.currentThread().loopLevel())
26         
27     @Slot()
28     def blocking_slot(self):
29         print('Blocking Queued connection')
30         print('In', QThread.currentThread().objectName(),
31             ', Loop level', QThread.currentThread().loopLevel())
32         QThread.sleep(10)
 1 # https://doc.qt.io/qt-6/qthread.html
 2 
 3 import sys
 4 
 5 from PySide6.QtCore import QThread, Slot, Qt
 6 from PySide6.QtWidgets import (QApplication, QPushButton,
 7     QLabel, QWidget, QVBoxLayout)
 8 from worker import Worker
 9 
10 
11 class Window(QWidget):
12     
13     def __init__(self):
14 
15         super().__init__()
16         
17         QThread.currentThread().setObjectName('Main thread')
18         
19         layout = QVBoxLayout()
20         self.setLayout(layout)
21         
22         self.button_auto = QPushButton('Auto connection')
23         self.button_direct = QPushButton('Direct connection')
24         self.button_queued = QPushButton('Queued connection')
25         self.button_blocking = QPushButton('Blocking Queued connection')
26 
27         self.label = QLabel()
28         self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
29         
30         self.emitting_thread = QThread()
31         self.emitting_thread.setObjectName('Emitting Thread')
32         
33         self.emitter = Worker()
34         self.emitter.moveToThread(self.emitting_thread)
35         
36         self.receiving_thread = QThread()
37         self.receiving_thread.setObjectName('Receiving Thread')
38         
39         self.receiver = Worker()
40         self.receiver.moveToThread(self.receiving_thread)
41         
42         self.emitter.auto_signal.connect(self.receiver.auto_slot, Qt.ConnectionType.AutoConnection)
43         self.emitter.direct_signal.connect(self.receiver.direct_slot, Qt.ConnectionType.DirectConnection)
44         self.emitter.queued_signal.connect(self.receiver.queued_slot, Qt.ConnectionType.QueuedConnection)
45         self.emitter.blocking_signal.connect(self.receiver.blocking_slot, Qt.ConnectionType.BlockingQueuedConnection)
46         
47         self.button_auto.clicked.connect(self.emitter.auto_signal)
48         self.button_direct.clicked.connect(self.emitter.direct_signal)
49         self.button_queued.clicked.connect(self.emitter.queued_signal)
50         self.button_blocking.clicked.connect(self.emitter.blocking_signal)
51         
52         self.emitting_thread.start()
53         self.receiving_thread.start()
54         
55         layout.addWidget(self.button_auto)
56         layout.addWidget(self.button_direct)
57         layout.addWidget(self.button_queued)
58         layout.addWidget(self.button_blocking)
59         layout.addWidget(self.label)
60     
61     @Slot()
62     def on_result_ready(self, result):
63         self.label.setText(result)
64         
65     def closeEvent(self, event):        
66         try:
67             self.emitting_thread.quit()
68             self.emitting_thread.wait()
69             self.receiving_thread.quit()
70             self.receiving_thread.wait()
71         except Exception as e:
72             print(e) 
73         event.accept()
74 
75 
76 if __name__ == '__main__':
77 
78     app = QApplication(sys.argv)
79     main_window = Window()
80     main_window.show()
81 
82     sys.exit(app.exec())
Connection Type Emitter Thread Receiver Thread Slot Invoked Slot Executed In Blocks Unique
Auto A A immediately when the signal is emitted A No
Auto A B when control returns to the event loop of the receiver’s thread B No
Direct A B immediately when the signal is emitted A No
Queued A B when control returns to the event loop of the receiver’s thread B No
Blocking Queued A B when control returns to the event loop of the receiver’s thread B No
Unique A A immediately when the signal is emitted A Yes
Unique A B when control returns to the event loop of the receiver’s thread B Yes