How to report progress and abort a long-running operation with QRunnable and QThreadPool
This article shows prime number validation that runs in a separate thread. Prime number validation is one example of a long operation that needs to be put into a separate thread in order not to freeze the graphical user interface. In this article, I am also going to explain how to report the progress of the calculation and how to abort it.
Note: This is a naive implementation of prime number validation. The example with prime numbers is here to show how to put a long-running calculation into a separate thread, rather than to provide the most efficient prime number validation. The specifics of threading in this particular implementation were devised based on Chapter 7, Advanced Qt Programming by Mark Summerfield and Qt Quarterly : Keeping the GUI Responsive. Summerfield's book shows two different ways of reporting progress, namely by subclassing QRunnable and calling QMetaObject::invokeMethod() or by subclassing QEvent and using QtConcurrent::run().
Introduction - basic background on threads
This section provides a basic background on threads without going into synchronization. You can skip this part if you are already familiar with threads. The main difference between a process and a thread is that each process is executed in a separate address space, whereas each thread uses the same address space of a process. A process can have multiple threads. Threads share the heap, but each thread of execution has its own call stack.
In our example, we will use a Qt's QThreadPool object to manage a collection of threads. The QThreadPool object of an application can be accessed by calling QThreadPool::globalInstance(). To be able to use one of the QThreadPool threads, we have to subclass QRunnable and implement the run() method. After that, we have to create an instance of the QRunnable subclass and pass it to QThreadPool::start(). This is shown in the code snippet below. The figure explains the stack allocations for different stages of code execution.
#include <QThreadPool> #include <QDebug> #include <QApplication> class MyRunnable : public QRunnable { public: MyRunnable():QRunnable(){} void run(){ for(int i = 0; i < 3; i++) qDebug() << "i " << i; } }; void print(){ QRunnable* printRunnable = new MyRunnable(); QThreadPool::globalInstance()->start(printRunnable); } int main(int argc, char *argv[]){ QApplication a(argc, argv); print(); return 0; }The program produces the output below. The print is being executed in a separate thread.
i 0
i 1
i 2
Little is guaranteed when it comes to threads. The only guarantee with threads is that each thread will start and each thread will run to completion. Within each thread, code executes in a predictable order. However, actions of different threads can mix in an unpredictable manner. This can be seen below.
#include <QThreadPool> #include <QDebug> #include <QApplication> class MyRunnable : public QRunnable { public: MyRunnable():QRunnable(){} void run(){ for(int i = 0; i < 3; i++) qDebug() << QThread::currentThreadId() << " i " << i; } }; int main(int argc, char *argv[]){ QApplication a(argc, argv); QThreadPool::globalInstance()->start(new MyRunnable); QThreadPool::globalInstance()->start(new MyRunnable); QThreadPool::globalInstance()->start(new MyRunnable); return 0; }The program produces the output below (you might see a slightly different output on your computer). Notice how a particular thread (e.g. 0x32c0) executes in a predictable order (0,1,2) but the ordering among the threads is rather unpredictable.
0x32c0 i 0
0x32f8 i 0
0x32f8 i 1
0x32f8 i 2
0x32c0 i 1
0x32c0 i 2
0x25a0 i 0
0x25a0 i 1
0x25a0 i 2
This section has been adapted from Sun Certified Programmer for Java 6 Exam 310-065 (2008) by Kathy Sierra and Bert Bates.
How to place prime number validation into a separate thread
Firstly, let's quickly summarize the definition of a prime number. A prime number is a natural number greater than 1 that is divisible only by 1 and itself. 0 and 1 are not prime numbers since they do not fulfill this criterion. Here you can see a couple of prime numbers which you can enter into our application:
12764787846358441471 - 20 digits
2305843009213693951 - 19 digits
1111111111111111111 - 19 digits
909090909090909091 - 18 digits
790738119649411319 - 18 digits
768614336404564651 - 18 digits
489133282872437279 - 18 digits
108086391056891903 - 18 digits
99194853094755497 - 17 digits
304250263527209 - 15 digits
To implement the prime number validation, together with the possibility of a progress report and abort we need to do the following:
- To perform the validation in a separate thread we need to subclass QRunnable, implement its run() method and use it with a QThreadPool object.
- To indicate abort or termination of the calculation we need to introduce a boolean 'stopped', stored in the prime widget and referenced by a QRunnable instance.
- To report the progress of the calculation from within the secondary thread we use QMetaObject::invokeMethod().
How to subclass QRunnable
The prime number validation is calculated by an instance of the class PrimeRunnable subclassed from QRunnable. The actual code for the validation of the number has to be put into the run() method. The run() was made private to prevent it from being called directly on instances of PrimeRunnable (rather than via QThreadPool::globalInstance()->start()).
Notice that we store a couple of attributes within the PrimeRunnable class. We store a pointer to the main widget so that we have a way of reporting progress. We also store the pointer to the boolean responsible for aborting the calculation (this variable will be changed from outside - i.e. by the main GUI thread). Next, we store the candidate for the prime number n, i.e. the number entered by the user. Last, we store the result of the calculation in m_prime. We also define a const int pIncrement = 1 (this means that we will be reporting progress by 1%, a value of 2 would mean that we report progress by 2%, etc.).
If you look into the public section of the class, you will see that the result can be retrieved by isPrime(). As you will shortly see in the source code of the PrimeWidget, this presumes that we disable auto-deletion of the runnable by the QThreadPool.
const int pIncrement = 1; class PrimeRunnable : public QRunnable { public: PrimeRunnable(QWidget* receiver, volatile bool *stopped, qulonglong n); bool isPrime() const {return m_prime;} private: void run(); qulonglong n; QWidget* receiver; volatile bool *m_stopped; bool m_prime; };
We use qulonglong to store prime number candidates. qulonglong i.e quint64, is a typedef for unsigned long long int, holding values from 0 to 18,446,744,073,709,551,615 which equals to 264 − 1. This is the upper limit for the numbers you can try out in this application.
The volatile keyword marks a variable that can be changed from outside the program or that can be modified by a different thread of the same program. The keyword prevents the compiler from optimizing the value (e.g by caching) which might result in an outdated value being used throughout the program. As Mark SummerField points out in Advanced Qt Programming (p.248), volatile is not suitable for threaded use with other data types (not even with ints).
The run() method naively checks whether the number is a prime or not. For this, it uses trialFactors starting from 2 until the square root of the number entered by the user. If the calculation has been canceled by the user, the method does not finish the loop and returns. We report the progress by calling QMetaObject::invokeMethod(). We do not report the progress for every trialFactor but rather in 1% intervals (valueIncrement stores the trialFactor that corresponds to a particular integer percentage). If the trialFactor divides the number entered by the user, the number is not prime, as a result, we do not change the default boolean value and return from the method. If the check finishes and no divisor is found, we set the m_prime to true. If the user enters 0 or 1 the method returns as these numbers are not considered to be prime.
void PrimeRunnable::run() { if(n == 0 || n == 1) return; qulonglong sRoot = sqrt(n); qulonglong valueIncrement = pIncrement*sRoot/100; for (qulonglong trialFactor = 2; trialFactor <= sRoot; trialFactor++) { if(*m_stopped) return; if(valueIncrement >=1 && trialFactor % valueIncrement == 0){ int pFinished = trialFactor*100 / sRoot; QMetaObject::invokeMethod(receiver, "updateProgressBar", Qt::QueuedConnection, Q_ARG(int, pFinished)); } if(n % trialFactor == 0) return; } m_prime = true; }
The progress of the execution in the secondary thread is reported via QMetaObject::invokeMethod() invoking PrimeWidget::updateProgressBar().
void PrimeWidget::updateProgressBar(int progressPercent){ if(stopped) return; this->progressBar->setValue(progressPercent); }
The method bool QMetaObject::invokeMethod((QObject* obj, const char* member, Qt::ConnectionType type, QGenericArgument val0,...QGenericArgument val9) simulates signal emission. It invokes the member (a signal or a slot name) on the object obj. It returns true if the member could be invoked and false if the member does not exist or the parameters did not match. The invocation can be either synchronous or asynchronous, with Qt::QueuedConnection, a QEvent will be sent and the member will be invoked as soon as the application enters the main event loop.
Implementation of the PrimeWidget
Below you can see the header of a subclass of a QWidget called PrimeWidget. The PrimeWidget stores a pointer to our runnable (called primeTask) and a boolean 'stopped' that signalizes whether the calculation is in progress or has been aborted or finished.
The class also contains a couple of important slots, namely:
- updateProgressBar() is invoked in regular intervals by an instance of PrimeRunnable via QMetaObject::invokeMethod()
- calculateIfPrime() instantiates a new PrimeRunnable and starts it from the QThreadPool object
- abort() aborts the calculation
- updateChildWidgets() enables or disables the child widgets based on whether the calculation started, has been cancelled, produced a result etc.
- checkIfDone() checks recursively in regular intervals whether the thread executing the prime number calculation is still active
class PrimeWidget : public QWidget { Q_OBJECT public: explicit PrimeWidget(QWidget *parent = 0); ~PrimeWidget(); private slots: void updateProgressBar(int progressPercent); void calculateIfPrime(); void abort(); void updateChildWidgets(); void checkIfDone(); protected: void closeEvent(QCloseEvent *); private: QLabel* numberLabel; QLabel* resultLabel; QLineEdit* numberEdit; QPushButton* calculateButton; QPushButton* abortButton; QProgressBar* progressBar; volatile bool stopped; PrimeRunnable* primeTask; };
In the PrimeWidget's constructor we initiate and enable/disable child widgets, i.e.just after we open the application, the "Check if Prime' button should be enabled, on the other hand, the 'Abort' button should be disabled. The notable part of the constructor is the progressBar, which we make invisible by default and only make it visible once we start the prime number validation. Notice the connections: we connect the calculateButton's signal clicked() to the slot calculateIfPrime() and the abortButton's signal clicked() to the slot abort(). Last, notice that I used the initialization list to initialize the pointer to the primeTask runnable.
PrimeWidget::PrimeWidget(QWidget *parent) : QWidget(parent), primeTask(0) { setWindowTitle("Prime number validator"); QVBoxLayout* mainLayout = new QVBoxLayout(this); QHBoxLayout* inputLayout = new QHBoxLayout; QHBoxLayout* buttonLayout = new QHBoxLayout; numberLabel = new QLabel(tr("Enter a number:")); numberEdit = new QLineEdit; inputLayout->addWidget(numberLabel); inputLayout->addWidget(numberEdit); mainLayout->addLayout(inputLayout); calculateButton = new QPushButton(tr("Check if prime")); abortButton = new QPushButton(tr("Abort")); abortButton->setEnabled(false); buttonLayout->addWidget(calculateButton); buttonLayout->addWidget(abortButton); mainLayout->addLayout(buttonLayout); resultLabel = new QLabel(tr("Prime?")); QFont f("Arial", 10, QFont::Bold); resultLabel->setFont(f); resultLabel->setAlignment(Qt::AlignCenter); resultLabel->setTextFormat(Qt::RichText); mainLayout->addWidget(resultLabel); progressBar = new QProgressBar; mainLayout->addWidget(progressBar); progressBar->setVisible(false); QObject::connect(calculateButton, SIGNAL(clicked()), this, SLOT(calculateIfPrime())); QObject::connect(abortButton, SIGNAL(clicked()), this, SLOT(abort())); }
The slot calculateIfPrime() is called whenever we press the calculateButton. The method checks the validity of the number entered by the user. It also adjusts the user interface, creates an instance of the PrimeRunnable, disables its auto-deletion (so that we can retrieve its result by calling PrimeRunnable::isPrime()) and starts the task via QThreadPool object. Last, it calls checkIfDone().
void PrimeWidget::calculateIfPrime(){ bool numberCorrect = false; qlonglong n = numberEdit->text().toULongLong(&numberCorrect); if(numberCorrect){ stopped = false; updateChildWidgets(); primeTask = new PrimeRunnable(this, &stopped, n); primeTask->setAutoDelete(false); QThreadPool::globalInstance()->start(primeTask); checkIfDone(); } else resultLabel->setText("<font color = '#CC0000'>" "You did not enter an usigned 64 bit integer.</font>"); }
Do not call QThreadPool::globalInstance()->waitForDone() after starting the secondary thread of execution with QThreadPool::globalInstance()->start(primeTask). This will cause the blocking of the main thread, i.e. you will not see any update of the progress bar. Instead, you have to call checkIfDone() in regular (in this case 100ms) intervals.
checkIfDone() checks recursively in regular intervals (100 ms) whether the thread executing the prime number validation is still active. If not, the boolean 'stopped' is set to true and the user interface is updated based on the result of the calculation. After retrieving the result, we delete the PrimeRunnable instance and reset the pointer of primeTask to 0 (this is necessary, because we create an instance of PrimeRunnable every time the user clicks on the "Check if prime' button, furthermore, we disabled the deletion of the runnable by the QThreadPool object).
void PrimeWidget::checkIfDone(){ if(QThreadPool::globalInstance()->activeThreadCount()) QTimer::singleShot(100, this, SLOT(checkIfDone())); else { if(!stopped) { stopped = true; updateChildWidgets(); if(primeTask->isPrime()) resultLabel->setText( "<font color = '#003582'>The number is prime.</font>"); else resultLabel->setText( "<font color = '#CC0000'>The number is not prime.</font>"); } if(primeTask){ delete primeTask; primeTask = 0; } } }
If the user aborts the calculation, we set the boolean 'stopped' to true to abort the calculation as soon as possible (we do not want to wait until the secondary thread checks all the numbers if the calculation has been aborted). If the secondary thread still happens to be active in the thread pool, we waitForDone(), i.e. we wait for the secondary thread to exit. After that, we enable/disable the child widgets and update the text of the resultLabel. Similarly to checkIfDone() we delete the instance of PrimeRunnable.
void PrimeWidget::abort(){ stopped = true; if (QThreadPool::globalInstance()->activeThreadCount()) QThreadPool::globalInstance()->waitForDone(); updateChildWidgets(); resultLabel->setText( "<font color = '#CC0000'>Calculation aborted.</font>"); if(primeTask){ delete primeTask; primeTask = 0; } }
I chose to store the result of the calculation in m_prime which can be retrieved by isPrime(). I did this in order to clearly indicate the functionality of the runnable. However, this meant that I had to disable the auto-deletion of the runnable by the QThreadPool object and manage the destruction on my own. You don't have to do this. You can report the result directly from the run() via QMetaObject::invokeMethod(). Like this, you won't have to delete the runnable yourself.
Last, we reimplement the closeEvent() method. Before terminating the application we set the boolean 'stopped' to true and wait for all secondary threads to exit. At the end, we call event->accept().
void PrimeWidget::closeEvent(QCloseEvent *event){ stopped = true; if(QThreadPool::globalInstance()->activeThreadCount()) QThreadPool::globalInstance()->waitForDone(); event->accept(); }
That's it! You can try out the prime number validation.
int main(int argc, char *argv[]) { QApplication a(argc, argv); PrimeWidget w; w.show(); return a.exec(); }