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
![]() |
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 using a custom Qt signal namedprogress. 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.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 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.
![]() |
You need to create a minimal working Qt multithreading application. |
- Create a
QObjectsubclass 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 namedprocess(); it prints a message and emits a custom signal namedfinished()before returning. TheWorkerclass declares anerror()signal, which could be emitted under error conditions duringprocess()execution.
Then, in the main window class:
Create a
QThreadobject namedbackground_thread(avoid naming itthread, as that name is already used). Useselfto make it the member of the main window, ensuring it remains in scope while running.Create a
Workerobject and move it tobackground_threadusingQObject.moveToThread(). Now,Worker.process()will execute inbackground_thread.-
Connect the appropriate signals and slots:
- Connect
background_thread.started()toworker.process(). This executesWorker.process()when the background thread starts. - Connect
Worker.finished()tobackground_thread.quit(). This quits the background thread whenWorker.process()returns. - Connect
Worker.finished()toWorker.deleteLater()method. This deletes the worker object some time after it emitsfinished(). - Connect
background_thread.finished()tobackground_thread.deleteLater(). This deletes the background thread some time after it finishes.
Start
background_threadusingQThread.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.
![]() |
You need to use Qt multithreading to search the filesystem for a file and report progress. |
- Create the worker class. The method,
process()uses Python’sos.walk()to traverse the filesystem. For each enumerated filesystem object, we emit a customprogresssignal (added toWorkeralongsidefinishedanderror). If the background thread receives an interruption request, we return fromprocess(), triggering the signal-slot chain to stop and delete both the worker and the background thread. Note how inWorker.process(), we access the current (background) thread via the staticQThread.currentThread()method.
In the main window class, create the thread object.
Create a
Workerobject and move it to theQThreadusingQObject.moveToThread().Use signals and slots to ensure proper creation and deletion: Starting the background thread triggers
Worker.process();QObject.finished()triggers bothQThread.quit()andWorker.deleteLater();QThread.finished()triggersQThread.deleteLater().Start the background thread. This occurs in
Window.on_start_button_clicked()creating newQThreadandWorkerobjects 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.
![]() |
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 class:
Create the worker thread.
Create the
Workerobject and move it to the the worker thread usingQObject.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 occur 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 to executeWorker.do_work().Override
QWidget.closeEvent()to quit the thread usingQThread.quit()andQThread.wait().
27.5 Walking the Filesystem reusing the QThread Object
![]() |
You need to use Qt multithreading to walk the filesistem but you reuse the same worker thread. |
- Create the worker class. There are several differences from the first walk filesystem example: We use a boolean flag
Worker.interruption_requestedinstead ofQThread.isInterruptionRequested(); addWorker.stop()andWorker.reset()to toggle the flag; guard each use withQMutexLOckerandQMutexfor thread safety.
In
Controller.__init__()create the worker thread object.Create the worker object 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 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
| 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 |

