
Software of Unknown Provenance (SOUP) in Medical Devices
In the projects we deliver at Somco for medical device manufacturers, the topic of SOUP almost always comes up. Whether […]

As applications get more complex and performance expectations rise, multithreading becomes essential. In my experience, Qt provides a powerful — though sometimes confusing — set of tools for working with threads, a cross platform implementation that’s suitable for many app scenarios in the broader Qt framework.
This article is based on a talk I gave at Qt World Summit and later at the Qt C++ Warsaw Meetup.
If you missed those events, this post is a written version of the same ideas — a walk through of how Qt’s multithreading model has evolved over time:
Need help with Qt development and multithreading? Reach out for expert support with QThread, QtConcurrent, or TaskTree to optimize your workflow and enhance your app’s performance.
Threading makes your application more responsive and faster by allowing expensive or blocking operations to run in parallel. This is crucial when dealing with GUI, as the main thread can handle user input smoothly while a secondary thread executes heavy computations in a multithreaded design. In practice, multithreaded applications use one thread for the main event loop and another thread for data processing, so threads based tasks don’t block the UI in the Qt threading system.
Over the years, Qt’s approach to multithreading has changed a lot. Early versions required manual thread handling through QThread, but newer releases introduced QtConcurrent and later, experimental frameworks like TaskTree. This reflects Qt’s philosophy — providing control for experts and simplicity for everyday use. If you look into the source code of these libraries, you’ll notice the clear separation between threading classes and logic.
QThread is an actual thread of execution. It’s similar to std::thread, but fully integrated with Qt’s event system. A typical implementation uses a QThread instance that manages the thread and its lifecycle.
Early on, the typical approach was to subclass QThread and override run() method:
class WorkerThread : public QThread {
Q_OBJECT
void run() override {
QString result;
/* ... expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread() {
auto workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
While this gives full control over new thread creation and lifecycle, it also introduces boilerplate and potential pitfalls — particularly when mixing signals, slots, and thread affinity.
One of the most common pitfalls when working with QThread is misunderstanding thread affinity. Each QObject belongs to the thread where it was created and if moved after connections have been setup, signals and slots may execute in an unexpected context. Always ensure objects are moved to the right thread before starting work: for example, the main application ensures its QObjects remain tied to the main thread for thread safe operation.
A cleaner alternative is to introduce a worker object and move it into a separate thread rather than subclassing QThread itself. This approach separates business logic from thread management and in some cases allows to move existing code to a background thread with minimal changes.
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
// Long-running task
emit progressChanged(30);
// More work here
emit workFinished();
}
signals:
void progressChanged(int progress);
void workFinished();
};
To use it:
auto thread = new QThread(); auto worker = new Worker(); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Worker::doWork); connect(worker, &Worker::workFinished, thread, &QThread::quit); connect(worker, &Worker::workFinished, worker, &Worker::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(worker, &Worker::progressChanged, this, &MainWindow::updateProgressBar); thread->start();
This pattern is still relevant today for long-running or continuously active background tasks that require their own event loops. Details such as lifetime management and cleanup are crucial when working with non main function thread logic.
When tasks are short-lived, managing multiple threads manually is wasteful. QThreadPool allows you to reuse threads and specify the maximum number of active workers:
class MyTask : public QRunnable {
void run() override {
qDebug() << "Hello world from thread";
}
};
QThreadPool::globalInstance()->start(new MyTask());
QThreadPool::globalInstance()->start([]() {
qDebug() << "Hello world from lambda";
});
Thread pools reduce overhead and simplify concurrency control. You can also adjust their size:
auto pool = new QThreadPool(); pool->setMaxThreadCount(8); pool->start(new MyTask()); qDebug() << "Active threads:" << pool->activeThreadCount();
Starting with Qt 6.9, both QThread and QThreadPool introduce Quality of Service (QoS) controls via the new setServiceLevel() API.
This feature lets you hint to the operating system whether a thread should favor performance or energy efficiency, which is particularly valuable on heterogeneous CPU architectures that includes performance cores and efficiency cores (e.g., big.LITTLE).
QThread::currentThread()->setServiceLevel(QThread::QualityOfService::High); QThreadPool::globalInstance()->setServiceLevel(QThread::QualityOfService::Eco);
These hints are currently effective on Windows and Apple platforms, but the API is cross-platform safe — on unsupported systems, the call has no effect.
This addition marks a subtle yet important step toward better performance and power-awareness in Qt’s multithreading system.
Modern Qt applications rarely need to manage threads manually. Instead, QtConcurrent provides high-level functions like run(), map() and filter() that handle threading automatically.
int myFunction() { return 42; }
QFuture future = QtConcurrent::run(&myFunction);
No thread objects, no cleanup — just asynchronous execution. Function returns are neatly wrapped in a QFuture, allowing you to wait for completions or monitor results.
QFuture represents the result of an asynchronous computation. It allows status checks, chaining and cancellation, and can be integrated with Qt GUI updates in thread safe manner.
QFuturefuture = QtConcurrent::run([]() { return 42; }); qDebug() << "The answer is:" << future.result(); // blocks until finished
For non-blocking updates, use QFutureWatcher:
QFuturefuture = QtConcurrent::run([]() { return 42; }); auto watcher = new QFutureWatcher (); watcher->setFuture(future); connect(watcher, &QFutureWatcher ::finished, [=]() { qDebug() << "The answer is:" << watcher->result(); watcher->deleteLater(); });
Starting with Qt 6, you can chain asynchronous operations using .then():
QFuturefuture = QtConcurrent::run([]() { return 42; }) .then([](int result) { return static_cast (result); }) .then([](QFuture fut) { return QString("The final result is %1").arg(fut.result()); });
This is modern and nice — almost like async/await.
QtConcurrent now supports exceptions and promises. This is key to building a robust multithreaded pipeline. By subclassing QException, you can safely propagate errors across threads — essential for robust threading classes:
class MyException : public QException {
public:
void raise() const override { throw *this; }
MyException *clone() const override { return new MyException(*this); }
};
And handle them elegantly:
QFuturefuture = QtConcurrent::run([]() -> int { throw MyException(); }) .onFailed([](const MyException&) { qDebug() << "MyException occurred"; return -1; });
You can even manually manage asynchronous progress using QPromise:
QFuturerunWithProgress() { return QtConcurrent::run([](QPromise &promise) { promise.setProgressRange(0, 100); for (int i = 0; i <= 100; ++i) { if (promise.isCanceled()) return; promise.setProgressValue(i); QThread::msleep(20); } promise.addResult(42); }); }
While QtConcurrent simplifies a lot, complex asynchronous workflows still require orchestration. This is where TaskTree comes in — a framework developed for Qt Creator that manages task dependencies, continuations and error policies.
Here’s a simple example:
const Group recipe {
sequential, // execution mode
stopOnError, // workflow policy
NetworkQueryTask(...),
Group {
parallel,
ConcurrentCallTask(...),
ConcurrentCallTask(...)
}
};
taskTree->setRecipe(recipe);
taskTree->start();
It even supports logical operators and conditionals:
const Group recipe {
task1 && task2,
task3 || task4,
!task
};
const Group conditional {
If (condTask1) >> Then { bodyTask1 }
>> ElseIf (condTask2) >> Then { bodyTask2 }
>> Else { bodyTask3 }
};
This is the next step in Qt’s multithreading evolution — less boilerplate and more readability with a focus on system design.
As a Qt contributor I recently reported and proposed the improvement QTBUG-131142 – “Make TaskTree a public API”.
The suggestion was accepted and will be implemented in Qt 6.11, so TaskTree will be more accessible for developers who want to use it in their own projects.
It’s great to see this framework move from an internal Qt Creator component to a public Qt API.
| Aspect | QThread / Worker Pattern | QtConcurrent / QFuture | TaskTree |
|---|---|---|---|
| Level of abstraction | Low – manual thread and object management | Medium – automatic threading, manual logic | High – fully declarative task orchestration |
| Typical use case | Long-running or continuously active background tasks | Short-lived or parallelizable operations (data processing, I/O) | Complex workflows with dependencies, conditional logic, or branching |
| Ease of use | Difficult – boilerplate and lifetime management required | Easy – minimal code, automatic cleanup | Very easy – configuration-based, no thread code |
| Thread control | Full control (start, stop, pause, event loops) | Limited – handled internally by QtConcurrent | Fully automated by the framework |
| Error handling | Manual (signals, mutexes, try/catch) | Built-in via QFuture / QException / QPromise | Integrated – supports workflow policies (stopOnError, continueOnError) |
| Progress reporting | Manual via custom signals | Built-in through QPromise | Built-in and aggregated across tasks |
| Performance tuning | Full control, but requires careful tuning | Managed automatically via thread pools | Policy-driven (parallel/sequential) |
| Best for | Fine-grained control or legacy code | Modern applications needing async execution | Large, modular systems needing declarative concurrency |
| Example in Qt | QThread, QObject::moveToThread() | QtConcurrent::run(), QFutureWatcher | TaskTree (used by Qt Creator) |
Each approach represents a different balance between control, safety, and simplicity.
The best choice depends not on personal preference, but on the nature of your workload and how much you want to abstract away thread management.
From QThread to TaskTree, Qt has come a long way in simplifying multithreading. Each layer builds on the previous one, abstracting away complexity while preserving power. If you’re still managing threads manually, try using QtConcurrent and TaskTree — your future self (and your UI thread) will thank you.
Multithreading can be scary at first — I’ve made all the classic mistakes myself. But once you understand Qt’s model and choose the right abstraction level for your use case, it’s one of the most beautiful and robust frameworks for concurrent programming.
Need help with your Qt project?
Get in touch for expert support in designing, optimizing, or scaling your Qt applications — from architecture to performance and beyond.
Let's face it? It is a challenge to get top Qt QML developers on board. Help yourself and start the collaboration with Somco Software - real experts in Qt C++ framework.
Discover our capabilities
In the projects we deliver at Somco for medical device manufacturers, the topic of SOUP almost always comes up. Whether […]

Robot Operating System (ROS) is an open-source framework that has become the de facto standard for robotics software development. Modern […]

“21 August 2025 –What a night! Somco Software organized a Qt C++ Warsaw meetup and [dot, dot, dot]”* —– *-This […]