Mastering Threading in Qt: How to Make Your UI Feel Smooth and Responsive
So, you’ve built a Qt app, and everything is great—until you introduce some long-running tasks. Suddenly, your app freezes, the UI becomes unresponsive, and users start rage-clicking.
What Are Threads?
A thread is like a mini-program running inside your application. The main thread (where the UI runs) is responsible for handling user interactions, drawing widgets, and responding to events. If you perform heavy computations or network operations on the main thread, the UI freezes, and users get frustrated.
To avoid that, Qt provides multiple ways to use threads:
- QThread – Create and manage threads manually.
- QtConcurrent – Run tasks asynchronously without dealing with thread management.
- QThreadPool – A thread pool for managing multiple worker threads efficiently.
We’ll focus on QThread, as it gives the most control and is widely used in Qt applications.
Creating a Background Thread with QThread
Let’s say we have a long-running task (like downloading a large file, parsing a massive JSON, or training a tiny AI model). Instead of freezing the UI, we’ll offload the work to a separate thread.
Example: A Background Thread That Updates the UI
Here’s how to create a worker thread that does some background work while updating a progress bar in the UI.
1. Create a Worker Class
A worker class must inherit from QObject
and move to a separate QThread
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #include <QObject>
#include <QThread>
#include <QDebug>
#include <QTimer>
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr) {}
signals:
void progress(int value); // Signal to update UI
void finished(); // Signal when work is done
public slots:
void doWork() {
for (int i = 0; i <= 100; ++i) {
QThread::sleep(1); // Simulate a heavy task
emit progress(i); // Send progress to UI
}
emit finished();
}
};
|
2. Use the Worker in a QThread
Now, we’ll set up our UI and connect the worker’s signals to update it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| #include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QProgressBar>
#include <QVBoxLayout>
#include <QThread>
#include "worker.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) {
auto *centralWidget = new QWidget(this);
auto *layout = new QVBoxLayout(centralWidget);
setCentralWidget(centralWidget);
progressBar = new QProgressBar();
progressBar->setRange(0, 100);
QPushButton *startButton = new QPushButton("Start Task");
layout->addWidget(progressBar);
layout->addWidget(startButton);
// Create worker and thread
QThread *workerThread = new QThread(this);
Worker *worker = new Worker();
worker->moveToThread(workerThread);
// Connect signals and slots
connect(startButton, &QPushButton::clicked, worker, &Worker::doWork);
connect(worker, &Worker::progress, progressBar, &QProgressBar::setValue);
connect(worker, &Worker::finished, workerThread, &QThread::quit);
connect(worker, &Worker::finished, worker, &Worker::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
workerThread->start();
}
private:
QProgressBar *progressBar;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}
#include "main.moc"
|
Key Ideas
Concept | Description |
---|
QThread | Manages a separate thread |
Signals & Slots | Enables safe communication between threads |
moveToThread() | Moves an object to another thread |
Queued Connection | Ensures thread-safe UI updates |
Stopping Threads | Use a QAtomicBool to stop safely |
References