The main thread should not be doing anything besides gui work. Long-running operations do not belong in the main thread at all. If you have a long-running operation, you should execute it asynchronously.
We can factor out some common long-operation features:
class LongOperationBase : public QObject {
Q_OBJECT
std::atomic_bool stop, running;
protected:
bool shouldRun() const { return !stop; }
virtual void compute() = 0;
public:
Q_SLOT void start() {
stop = false;
emit started();
QtConcurrent::run([this]{
if (running || stop) return;
running = true;
compute();
running = false;
});
}
LongOperationBase() {}
LongOperationBase(QProgressDialog *pd) {
connectTo(pd);
}
bool isRunning() const { return running; }
Q_SLOT void cancel() { stop = true; } // thread-safe
Q_SIGNAL void started();
Q_SIGNAL void hasRange(int);
Q_SIGNAL void hasProgress(int);
void connectTo(QProgressDialog *pd) {
using B = LongOperationBase;
connect(this, &B::started, pd, &QProgressDialog::show);
connect(this, &B::hasRange, pd, &QProgressDialog::setMaximum);
connect(this, &B::hasProgress, pd, &QProgressDialog::setValue);
connect(pd, &QProgressDialog::canceled, this, &B::cancel, Qt::DirectConnection);
}
};
Suppose that you have the following long operation - the input and output data types are given only as an example. Each loop iteration should take 1-10ms to minimize the overhead of checking the status and emitting progress. You can easily perform these checks every M
iterations.
struct LongOperation final : LongOperationBase {
std::vector<int> input;
std::vector<double> output;
using LongOperationBase::LongOperationBase;
void compute() override {
size_t const N = input.size();
size_t const M = 1;
emit hasRange(N);
for (size_t i = 0, j = 0; i < N; ++i, ++j) {
// operate on input, generate output
...
if (j == (M-1)) {
emit hasProgress(i);
if (!shouldRun()) break;
j = 0;
}
}
}
};
Then, to execute it asynchronously:
class Window : public QWidget {
Q_OBJECT
QProgressDialog m_progress{this};
LongOperation m_operation{&m_progress};
...
public:
Window(QWidget * parent = {}) : QWidget(parent) {}
void runLongOperation() {
m_operation.start();
}
...
};
An alternative approach using the QFuture
system is given in this answer, but it's not complete enough to streamline the progress indications. The above QObject
-based approach is a workable solution in the interim.