AsyncProgressDialog
AsyncProgressDialog Documentation

Introduction

Although computers today have a number of cores and allow efficient asynchonous programming, some software developers still tend to keep time consuming operations in the GUI thread. The reason for this may be lack of experience with multi-threading programming or just pure lazyness. As a result, their applications are not responsive, which has a great impact on the usability as the application seems frozen to the user. The aim of this project is to provide developers with a simple-to-use toolbox for doing computations and long operations in a worker thread and thus leaving GUI thread free for interaction. The toolbox provide a few basic progress widgets to inform the user about the state of the operation and a simple way to cancel the long operation at some point by the user.

Download

The source code of this library is available on GitHub. The code is licenced under MIT licence.

Converting blocking code into non-blocking code

This is a typical (and wrong) example of synchonous operation and a progress dialog in Qt:

QProgressDialog dlg(this);
dlg.setLabelText("Progress");
dlg.setRange(0, 20);
dlg.setWindowModality(Qt::WindowModal);
dlg.show();
for (int i = 0; i < 20; i++)
{
dlg.setValue(i);
if (dlg.wasCanceled())
break;
QThread::msleep(500); // do a lengthy operation
}

We generally want to avoid such code as the application become unresponsive during the synchonous operation. Using the AsyncProgressDialog library, one can easily transform the code into the following asynchronous form:

adlg.setLabelText("Progress");
adlg.addTask([](auto thread)
{
thread->setRange(0, 10);
for (int i = 0; i < 10; i++)
{
thread->setValue(i);
if (thread->isCanceled())
break;
QThread::msleep(1000); // do a lengthy operation
}
thread->setValue(10);
});
adlg.exec();

As you may have noticed, the amount of code is similar in both examples whereas the latter example gains the benefit from being run asynchronously and thus not blocking the GUI thread. The trick is to move lengthy and possibly blocking code as well as the updates of the progress into a lambda function, which is run on the background. The lambda function has one input parameter, which can be used for sending updates to the progress widget and checking for cancel requests. Usage of this object is fully thread-safe as internally Qt::QueuedConnection is used to connect thread signals slots of progress widgets.

Input and output values

Input values can be sent into the lambda function using the capture statemet. The user is required to handle possible race conditions. In the simple example with one GUI and one worker no race conditions are likely to happen as the GUI thread waits in QDialog::exec() method until the worker thread has finished. In case or multiple threads the user has to protect shared resources using mutexes or other synchronization technique. Another option is to simply pass copies into lambdas if the copy is cheap.

Output values can be returned by return statement of the lambda function and accessed using APD::FunctionThread object returned from APD::AsyncProgressDialog::addTask() method, see the example below.

auto thread = adlg.addTask([](auto thread) {
thread->setRange(0, 10);
// The thread is going to calculate a summary and then return it
int sum = 0;
for (int i = 0; i < 10; i++)
{
thread->setValue(i);
if (thread->isCanceled())
break;
sum += i; // Calculate sum
QThread::msleep(100);
}
thread->setValue(10);
sum += 10; // Calculate sum
return sum; // Return sum variable, it can be later accesed using thread->result() method
});
// Task result is not ready yet.
// Calling FunctionalThread::result() is thread safe.
assert(thread->result() == std::nullopt);
adlg.exec();
// Now the task has finished, we can safely access its result
QMessageBox::information(this, QString("Resut"), QString("Result is %1").arg(*thread->result()));

Task thread

APD::AsynchProgressDialog is capable of running mutiple threads. Each thread is associated with a progress widget or a collection of progress widgets and the progress is updated automatically from within the thread. User may supply their own implementation of APD::TaskThread or use the lambda functions as decribed above.

Progress widgets

By default, the dialog displays a simple progress bar (APD::ProgressBar) for each task added to the dialog. However, AsynchProgressDialog offers multiple progress widgets, which are capable of displaying not only progress but also text (APD::ProgressLabel and APD::ProgressOutput), velocity plot (APD::ProgressVelocityPlot), or time estimates (APD::ProgressEstimate). The widgets can be combined together into a composite progress widget displaying progress from a single thread in APD::ProgressWidgetContainer. A factory is provided, which is capable of creating some popular combinations of progress widgets.

Examples

This section shows a few exmaples of what is the AsyncProgressDialog library capable of.

Progress label

ProgressLabelExample.png

The following code shows a progress dialog with a progress label. The label is set from within the task thread and may tell the user details about ongoing operation, for example, the name of the file, which is being copied.

adlg.addTask([](APD::TaskThread* thread) {
thread->setRange(0, 10);
for (int i = 0; i < 10; i++)
{
thread->setValue(i);
thread->setText(QString("Processing %1/10 ...").arg(i+1));
if (thread->isCanceled())
break;
QThread::msleep(1000);
}
thread->setValue(10);
}, APD::ProgressWidgetFactory::createProgressBar("Progress", APD::AdditionalWidget::Label));
adlg.exec();

Progress estimate

ProgressEstimateExample.png

Progress estimate widget is capable of automatically calculating and displaying estimated remaining time. The estimate works best if calls to setValue() method in the thread are evenly distributed.

adlg.addTask([](APD::TaskThread* thread) {
thread->setRange(0, 1000);
for (int i = 0; i < 1000; i++)
{
thread->setValue(i);
if (i % 10 == 0)
thread->setText(QString("Processing %1/100 ...").arg(i/10 + 1));
if (thread->isCanceled())
break;
QThread::msleep(100);
}
thread->setValue(10);
}, APD::ProgressWidgetFactory::createProgressBar(APD::AdditionalWidget::Estimate | APD::AdditionalWidget::Output));
adlg.exec();

Velocity example

VelocityExample.png

Velocity bar is somewhat similar to windows 7 copy file progress widget. It shows progress as well as current velocity and velocity history plot.

adlg.addTask([](APD::TaskThread* thread) {
thread->setRange(0, 150);
for (int i = 0; i < 150; i++)
{
thread->setValue(i, 2 + std::abs(2 * std::sin(i / 10.0)));
if (thread->isCanceled())
break;
QThread::msleep(80);
}
thread->setValue(150);
adlg.exec();

Mutiple progress bars example

MultiProgressExample.png

The following code shows how the progress dialog can cope with mutlitple threads showing a progress bar for each task as wel as a progress bar for total progress.

adlg.addTask(new MyThread(10, 1000, &adlg), APD::ProgressWidgetFactory::createProgressBar("Progress 1"));
adlg.addTask(new MyThread(20, 500, &adlg), APD::ProgressWidgetFactory::createProgressBar("Progress 2"));
adlg.addTask(new MyThread(80, 100, &adlg), APD::ProgressWidgetFactory::createProgressBar("Progress 3"));
adlg.addTask(new MyThread(120, 50, &adlg), APD::ProgressWidgetFactory::createProgressBar("Progress 4"));
adlg.addTask(new MyThread(6, 2000, &adlg), APD::ProgressWidgetFactory::createProgressBar("Progress 5"));
adlg.setOverallProgress(true);
adlg.exec();