27. Multithreading - moveToThread
Threads of execution let you execute your code concurrently while sharing the program’s memory and other resources. There are two primary use cases for threads:
- Accelerate processing by utilizing multiple processor cores,
- Maintain GUI responsiveness by offloading long-running tasks to background threads.
In the following examples, we focus on the second use case. First, however, let’s demonstrate the problem by showing a non-responsive Qt GUI.
27.1 Blocking the Qt GUI: How Not to Do It
![]() |
You need to search the filesystem for a file and report progress. |

Create the task. In the example, we use
os.walk()within aforloop to search the file system for a non-existent file. We attempt to report progress after each iteration by emitting a custom Qt signal namedprogress(). This fails because the loop blocks the Qt event loop. Even thoughprogress()is emitted and its slot run immediately, the window can’t repaint or respond to input until the loop returns control to the event loop. Because 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.Create a push button.
Create a slot that starts the long-running task when the push button is clicked.

27.2 A Minimal Working Example
The previous example shows 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.
![]() |
You need to create a minimal working Qt multithreading application. |

- Create a
QObjectsubclass that contains the slot/method to be executed in a background thread (i.e. a thread other than the GUI thread). In the example the slot is namedprocess(). It prints a message and emits a custom signal namedfinished()before returning. TheWorkerclass declares anerror()signal that can be emitted if an error occurs duringprocess()execution.

Then, in the main window class:
Create a
QThreadobject namedbackground_thread(avoid naming it simplythread, as that name is already used -QObject.thread()which returns the thread in which the object lives). Store it as a member of the main window (self.background_thread) so that it does not go out of scope whenon_button_clicked()returns.Create a
Workerobject and move it tobackground_threadwithQObject.moveToThread(). From this point on,Worker.process()will execute in the background thread.-
Connect the appropriate signals and slots:
- (2)
background_thread.started()->worker_obj.process(). This makesWorker.process()execute when the background thread starts. - (3)
worker_obj.finished()->background_thread.quit(). This stops the background thread event loop whenWorker.process()returns. - (4)
worker_obj.finished()->worker_obj.deleteLater()method. This schedules the worker for deletion. - (5)
background_thread.finished()->background_thread.deleteLater(). This schedules the thread object for deletion.
Start the background thread with
QThread.start()(1).

With these connections, process() runs as soon as the thread starts, the thread quits when the method returns, and both the worker and thread object are cleaned up automatically, while the GUI remains responsive.
27.3 Walking the Filesystem
The moveToThread() template provides a basic structure, but the worker does nothing useful. For a more practical example, we make Worker.process() traverse the filesystem with os.walk() starting from the root. This can take a long time and would freeze the GUI if it ran on the main thread.
![]() |
You need to use Qt multithreading to search the filesystem for a file and report progress. |

- Create the worker class. The
process()method uses Python’sos.walk()to traverse the filesystem. For each enumerated filesystem object, it emits a customprogress()signal (declared alongsidefinished()anderror()). If the background thread receives an interruption request,process()returns early, which triggers the usual signal-slot chain that stops and deletes both the worker and the background thread. Note that insideprocess(), we obtain the current (background) thread with the static methodQThread.currentThread().

In the main window class, create the thread object.
Create a
Workerobject and move it to the thread withQObject.moveToThread().-
Wire the lifetime signals exactly as in the minimal example:
- thread started -> worker process,
- worker finished -> thread quit,
- worker finished -> worker delete later,
- thread finished -> thread delete later.
Start the background thread. In this example the start happens inside
Window.on_start_button_clicked()so a newQThreadandWorkerare created each time the start button is pressed.

We also 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 previous examples a new background thread is created each time the task runs. The official QThread documentation example, however, creates both the thread and the worker once, in the main class constructor. Let’s reproduce that pattern.
![]() |
Your task is to recreate the official Qt multithreading example in PySide6. |

- Create the worker class. The background method is
do_work(), matching theQThreaddocumentation.

Then, in the main window (controller) class:
Create the worker thread.
Create the
Workerand move it to the worker thread withQObject.moveToThread().-
Connect signals and slots:
QThread.finished()toQObject.deleteLater()for worker deletion on thread finish,Controller.operate()toWorker.do_work()to start work via custom signal,Worker.result_ready()toController.handle_result()for handling results.
Start the worker thread. Steps 2-5 are performed in
Controller.__init__(), so the thread and the worker persist until the main window closes or explicit deletion.On button click, emit the
operate()signal. This causesWorker.do_work()to run.Reimplement
QWidget.closeEvent()to quit the thread usingQThread.quit()andQThread.wait().
27.5 Walking the Filesystem While Reusing the QThread
![]() |
You need to walk the filesystemwith Qt multithreading while reusing the same worker thread. |

-
Create the worker class. Several differences from the earlier filesystem example appear here:
- A boolean flag
interruption_requestedis used instead ofQThread.isInterruptionRequested() - We add
Worker.stop()andWorker.reset()methods to toggle the flag - Every access to the flag is protected by a
QMutexLockerandQMutexfor thread safety.

In
Controller.__init__()create the worker thread object.Create the worker and move it to the worker thread using
QObject.moveToThread().Connect signals and slots. Main class
operate()signal triggersWorker.do_work().Start the worker thread.
On Start button click, reset the worker with
Worker.reset()and emitoperate().On Cancel button click, stop the worker with
Worker.stop().Quit the thread when the main window closes.
But why did we use a mutex-guarded boolean flag? QThread.requestInterruption() is a one-shot mechanism: 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 a QMutex is required.
27.6 Signals and Slots Across Threads


| Connection Type | Threads | Slot Invoked | Executed In | Blocks Emitter |
|---|---|---|---|---|
| Auto | Same (A->A) | immediately when emitted | A | No |
| Auto | Different (A->B) | when control returns to B’s event loop | B | No |
| Direct | Same (A->A) | immediately when emitted | A | No |
| Direct | Different (A->B) | immediately when emitted | A | No |
| Queued | Same (A->A) | when control returns to A’s own event loop | A | No |
| Queued | Different (A->B) | when control returns to B’s event loop | B | No |
| Blocking Queued | Same (A->A) | deadlocks | - | Deadlock |
| Blocking Queued | Different (A->B) | when control returns to B’s event loop | B | Yes |
Any of the four connection types above can be combined with Qt.UniqueConnection (e.g., Qt.AutoConnection | Qt.UniqueConnection) to make connect() reject a duplicate connection.
Qt lets you write signal.connect(slot, Qt.ConnectionType.BlockingQueuedConnection) between two objects that live in the same thread, but doing so hangs that thread permanently. A blocking queued connection posts an event to the receiver’s event loop and then blocks the emitting thread until that event has been processed. When both sender and receiver are in the same thread, the event loop that would process it is the one you have just blocked. Qt detects the self-connection at runtime and prints a warning, but the call still blocks forever regardless. In the example above, the sender and receiver are in two different background threads, but if you commented these two lines:
1 # self.emitter.moveToThread(self.emitting_thread)
2 # self.receiver.moveToThread(self.receiving_thread)
you would get this message printed in the terminal before the application is blocked:
1 Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is Worker(0x17b9aed2c10), receiver is Worker(0x17b9aed2d30)
We deal with connection types in more detail in Chapter 32.

