28. Using a QThread subclass

Perhaps the most important thing to remember about QThread is that a QThread object does not represent an operating system thread - it is a thread manager. In practice, this means only the QThread.run() method executes in the new thread. All other QThread methods run in the thread that created the QThread object.

Another point when subclassing QThread is that a QThread subclass does not start its event loop unless you explicitly call QThread.exec() in your QThread.run() implementation.

28.1 A Minimal Example

There are two ways to use QThread to run a background task:

  • Create a worker object and move it to a background thread using QObject.moveToThread().
  • Create a QThread subclass and override its run() method.

The examples from the previous chapter used QObject.moveToThread(). Now let’s examine a minimal QThread subclass.

An icon of a clipboard-list1

You need to provide a working multithreading application by subclassing QThread.

  1. Create a QThread subclass (named WorkerThread in the example) and override its run() method. Add custom signals to the subclass and emit them from run() to communicate with the main thread.
  1. In your main window class, create a WorkerThread instance.

  2. Connect the WorkerThread signals to main window slots. Also connect WorkerThread.finished() to WorkerThread.deleteLater() for clean thread exit.

  3. Create slots in the main window to handle WorkerThread signals. Here, we set a QLabels text to “Hello, World” on WorkerThread.result_ready().

  4. Start the worker thread.

When creating a plain QThread object (as in moveToThread() examples) the sequence is:

  • Call QThread.start() from the main thread.
  • The background QThread emits started().
  • QThread.start() invokes QThread.run().
  • QThread.run() calls QThread.exec().
  • QThread.exec() enters the thread-local event loop, waiting for QThread.exit() or QThread.quit().
  • Call QThread.exit() or QThread.quit() to stop.
  • The event loop stops, emitting QThread.finished()
  • QObjects with QObject.deleteLater() are deleted.

(Remember that in moveToThread() examples, we connected QThread.started() to a worker slot for immediate execution; and worker finished() to QThread.quit() for thread completion.)

When subclassing QThread, no local event loop unless QThread.exec() is called in run(). Without it, classes that need an event loop (e.g., QTimer, QTcpSocket, QProcess) won’t work. However, the thread object will still be able to emit signals.

In this example, Window.start_work_in_a_thread() runs in the main thread (QThread.loopLevel() = 1, event loop active). WorkerThread.__init__() also runs in the main thread, while only WorkerThread.run() executes in the worker thread (QThread.loopLevel() = 0, no event loop).

28.2 Walking the Filesystem

An icon of a clipboard-list1

Now let’s walk the filesystem using a QThread subclass.

  1. Create a QThread subclass named WorkerThread and override run(). In run(), print the current thread object name to confirm execution in the worker thread, then use os.walk() for recursive traversal. Emit progress() with each object’s name as the argument.
  1. On start button click, create a WorkerThread object and set its name to “Worker thread”.

  2. Connect signals to slots: WorkerThread.progress() to Window.on_progress() for label updates; WorkerThread.finished() to WorkerThread.deleteLater() for cleanup.

  3. Handle signals. On progress() update the label; On cancel request interruption with QThread.requestInterruption() and wait for finish.

  4. Start the worker thread on each start button click.

Override Qwidget.closeEvent() to ensure thread deletion on window close. Note that we didn’t need to call QThread.quit() but only QThread.wait() since no event loop runs.