Example: rename progressdialog to primecounter and modernize it

The previous example finished way too quickly and provided no real
value in regards to API understanding. Previously, QtConcurrent::map
was used, which was also used in other examples. We are now using
QtConcurrent::filterReduce to demonstrate other functionality.

Task-number: QTBUG-111165
Pick-to: 6.5 6.5.0
Change-Id: Ibd6eb119d0711cddfe8b211d460e9d67d6ce95c3
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
Dennis Oberst 2023-02-16 18:14:38 +01:00
parent 4a9696aa05
commit 8352756d27
15 changed files with 494 additions and 86 deletions

View File

@ -40,7 +40,6 @@ manifestmeta.android.names = "Qt3D/Qt 3D: Basic Shapes C++ Example" \
"QtBluetooth/Bluetooth Scanner Example" \
"QtBluetooth/QML Bluetooth Scanner Example" \
"QtCharts/*" \
"QtConcurrent/QtConcurrent Progress Dialog Example" \
"QtDataVisualization/Audiolevels Example" \
"QtDataVisualization/Qt Quick 2 Scatter Example" \
"QtDataVisualization/Qt Quick 2 Surface Multiseries Example" \

View File

@ -6,6 +6,6 @@ if(NOT TARGET Qt6::Concurrent)
endif()
if(TARGET Qt6::Widgets)
qt_internal_add_example(imagescaling)
qt_internal_add_example(progressdialog)
qt_internal_add_example(primecounter)
qt_internal_add_example(wordcount)
endif()

View File

@ -1,36 +1,39 @@
# Copyright (C) 2022 The Qt Company Ltd.
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(progressdialog LANGUAGES CXX)
project(primecounter LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/progressdialog")
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/qtconcurrent/primecounter")
find_package(Qt6 REQUIRED COMPONENTS Concurrent Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(progressdialog
qt_add_executable(primecounter
main.cpp
primecounter.ui
primecounter.cpp
primecounter.h
)
set_target_properties(progressdialog PROPERTIES
WIN32_EXECUTABLE FALSE
set_target_properties(primecounter PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(progressdialog PRIVATE
target_link_libraries(primecounter PRIVATE
Qt6::Concurrent
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS progressdialog
install(TARGETS primecounter
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,88 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example primecounter
\meta tags {widgets, threads}
\title Prime Counter
\ingroup qtconcurrentexamples
\brief Demonstrates how to monitor the progress of concurrent operations.
The following example demonstrates how to create an interactive and
non-blocking QtWidgets application using the QFutureWatcher class and the
\l {Concurrent Filter-Reduce} {filteredReduced} functions from
\l {Qt Concurrent}. With this example, the user can create a QList of
integers that can be resized. The list will be automatically filled with
natural numbers starting from 1 up to n. The program will then check for
prime numbers within the list and display the total count of prime numbers
found.
\image primecounter.png
\include examples-run.qdocinc
\section1 Setting up the connections
The \l {Qt Concurrent} library provides the
\l {Concurrent Filter-Reduce} {filteredReduced} functions, which can operate
in two modes:
\l {QtConcurrent::ReduceOption} {OrderedReduce and UnorderedReduce}. In
\c OrderedReduce mode, the reducing function is called in the order of the
original sequence, whereas in \c UnorderedReduce mode, the elements are
accessed randomly.
After configuring the UI with the desired elements, it is necessary to
connect them to the signals of the concurrent operations using the Qt
\l {Signals & Slots} mechanism. In this example, we use the QFutureWatcher
class to monitor the progress of the concurrent operations and provide the
signals required to implement the interactive GUI.
\dots
\snippet primecounter/primecounter.cpp 1
\dots
The QFutureWatcher class plays a vital role in this example as it provides
the signals required to update the UI in response to changes in the
concurrent operations.
\section1 Starting the concurrent operation
After connecting all the \l {Signals & Slots}, and when the user presses
the QPushButton, the \c {start()} function is called.
In the \c {start()} function, we call the
\l {Concurrent Filter-Reduce} {filteredReduced} function from Qt Concurrent
and set the future on the QFutureWatcher member. To ensure that this
operation runs truly concurrently, we specify a separate QThreadPool as the
first parameter. This approach also avoids any possible blocking in the
global thread pool. We pass the QList of integers as the container, a
static filter and reduce function, and finally the
\l {QtConcurrent::} {ReduceOption} flag.
\dots
\snippet primecounter/primecounter.cpp 2
\dots
Let's examine the filter and reduce functions. These functions are declared
static in this example since they do not depend on any member variable.
However, they could easily be specified as lambdas or member functions.
The filter function marks elements for subsequent reduction with the reduce
function. This implementation is a simple prime filter. As this function
takes a const reference as an argument, it allows thread-safe operation on
the container it operates on.
\dots
\snippet primecounter/primecounter.cpp 3
\dots
The reduce function takes a modifiable reference of the same type as the
container it operates on as its first parameter. The second parameter is the
previously filtered element from the filter function. In this example, we
count the number of primes.
\dots
\snippet primecounter/primecounter.cpp 4
\dots
*/

View File

@ -0,0 +1,18 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets/qapplication.h>
#include "primecounter.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
app.setOrganizationName("QtProject");
app.setApplicationName(QApplication::translate("main", "Prime Counter"));
PrimeCounter dialog;
dialog.setWindowTitle(QApplication::translate("main", "Prime Counter"));
dialog.show();
return app.exec();
}

View File

@ -0,0 +1,139 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "primecounter.h"
#include "ui_primecounter.h"
PrimeCounter::PrimeCounter(QWidget *parent)
: QDialog(parent), stepSize(100000), ui(setupUi())
{
// Control the concurrent operation with the QFutureWatcher
//! [1]
connect(ui->pushButton, &QPushButton::clicked,
this, [this] { start(); });
connect(&watcher, &QFutureWatcher<Element>::finished,
this, [this] { finish(); });
connect(&watcher, &QFutureWatcher<Element>::progressRangeChanged,
ui->progressBar, &QProgressBar::setRange);
connect(&watcher, &QFutureWatcher<Element>::progressValueChanged,
ui->progressBar, &QProgressBar::setValue);
//! [1]
}
PrimeCounter::~PrimeCounter()
{
watcher.cancel();
delete ui;
}
//! [3]
bool PrimeCounter::filterFunction(const Element &element)
{
// Filter for primes
if (element <= 1)
return false;
for (Element i = 2; i*i <= element; ++i) {
if (element % i == 0)
return false;
}
return true;
}
//! [3]
//! [4]
void PrimeCounter::reduceFunction(Element &out, const Element &value)
{
// Count the amount of primes.
Q_UNUSED(value);
++out;
}
//! [4]
//! [2]
void PrimeCounter::start()
{
if (ui->pushButton->isChecked()) {
ui->comboBox->setEnabled(false);
ui->pushButton->setText(tr("Cancel"));
ui->labelResult->setText(tr("Calculating ..."));
ui->labelFilter->setText(tr("Selected Reduce Option: %1").arg(ui->comboBox->currentText()));
fillElementList(ui->horizontalSlider->value() * stepSize);
timer.start();
watcher.setFuture(
QtConcurrent::filteredReduced(
&pool,
elementList,
filterFunction,
reduceFunction,
currentReduceOpt | QtConcurrent::SequentialReduce));
//! [2]
} else {
watcher.cancel();
ui->progressBar->setValue(0);
ui->comboBox->setEnabled(true);
ui->labelResult->setText(tr(""));
ui->pushButton->setText(tr("Start"));
ui->labelFilter->setText(tr("Operation Canceled"));
}
}
void PrimeCounter::finish()
{
// The finished signal from the QFutureWatcher is also emitted when cancelling.
if (watcher.isCanceled())
return;
auto elapsedTime = timer.elapsed();
ui->progressBar->setValue(0);
ui->comboBox->setEnabled(true);
ui->pushButton->setChecked(false);
ui->pushButton->setText(tr("Start"));
ui->labelFilter->setText(
tr("Filter '%1' took %2 ms to calculate").arg(ui->comboBox->currentText())
.arg(elapsedTime));
ui->labelResult->setText(
tr("Found %1 primes in the range of elements").arg(watcher.result()));
}
void PrimeCounter::fillElementList(unsigned int count)
{
// Fill elementList with values from [1, count] when starting the calculations.
auto prevSize = elementList.size();
if (prevSize == count)
return; // Nothing to do here.
auto startVal = elementList.empty() ? 1 : elementList.back() + 1;
elementList.resize(count);
if (elementList.begin() + prevSize < elementList.end())
std::iota(elementList.begin() + prevSize, elementList.end(), startVal);
}
Ui::PrimeCounter* PrimeCounter::setupUi()
{
Ui::PrimeCounter *setupUI = new Ui::PrimeCounter;
setupUI->setupUi(this);
setModal(true);
// Set up the slider
connect(setupUI->horizontalSlider, &QSlider::valueChanged,
this, [setupUI, this] (const int &pos) {
setupUI->labelResult->setText("");
setupUI->labelSize->setText(tr("Elements in list: %1").arg(pos * stepSize));
});
setupUI->horizontalSlider->setValue(30);
// Set up the combo box
setupUI->comboBox->insertItem(0, tr("Unordered Reduce"), QtConcurrent::UnorderedReduce);
setupUI->comboBox->insertItem(1, tr("Ordered Reduce"), QtConcurrent::OrderedReduce);
auto comboBoxChange = [this, setupUI](int pos) {
currentReduceOpt = setupUI->comboBox->itemData(pos).value<QtConcurrent::ReduceOptions>();
setupUI->labelFilter->setText(tr("Selected Reduce Option: %1")
.arg(setupUI->comboBox->currentText()));
};
comboBoxChange(setupUI->comboBox->currentIndex());
connect(setupUI->comboBox, &QComboBox::currentIndexChanged, this, comboBoxChange);
return setupUI;
}

View File

@ -0,0 +1,49 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef PRIMECOUNTER_H
#define PRIMECOUNTER_H
#include <QtWidgets/qdialog.h>
#include <QtCore/qfuturewatcher.h>
#include <QtConcurrent/qtconcurrentfilter.h>
#include <QtConcurrent/qtconcurrentreducekernel.h>
QT_BEGIN_NAMESPACE
class QLabel;
class QProgressBar;
namespace Ui {
class PrimeCounter;
}
QT_END_NAMESPACE
class PrimeCounter : public QDialog
{
Q_OBJECT
using Element = unsigned long long;
public:
explicit PrimeCounter(QWidget* parent = nullptr);
~PrimeCounter() override;
private:
static bool filterFunction(const Element &element);
static void reduceFunction(Element &out, const Element &value);
void fillElementList(unsigned int count);
Ui::PrimeCounter* setupUi();
private slots:
void start();
void finish();
private:
QList<Element> elementList;
QFutureWatcher<Element> watcher;
QtConcurrent::ReduceOptions currentReduceOpt;
QElapsedTimer timer;
QThreadPool pool;
unsigned int stepSize;
Ui::PrimeCounter *ui;
};
#endif //PRIMECOUNTER_H

View File

@ -0,0 +1,9 @@
QT += concurrent widgets
SOURCES += main.cpp primecounter.cpp
HEADERS += primecounter.h
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/primecounter
INSTALLS += target
FORMS += primecounter.ui

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PrimeCounter</class>
<widget class="QDialog" name="PrimeCounter">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>454</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>20</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="labelInfo">
<property name="font">
<font>
<pointsize>11</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Select a reducing option and measure the speed</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout2">
<property name="topMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout1">
<property name="topMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Start</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="labelFilter">
<property name="text">
<string>Filter Label</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout3">
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelSize">
<property name="text">
<string>size</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>5</number>
</property>
<property name="indent">
<number>10</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="labelResult">
<property name="text">
<string>Result Label</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,14 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example progressdialog
\title QtConcurrent Progress Dialog Example
\brief Demonstrates how to monitor the progress of the active processes.
\ingroup qtconcurrentexamples
The QtConcurrent Progress Dialog example shows how to use the
QFutureWatcher class to monitor the progress of a long-running operation.
\image qtconcurrent-progressdialog.png
*/

View File

@ -1,53 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include <QtConcurrent>
#include <functional>
using namespace QtConcurrent;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
const int iterations = 20;
// Prepare the list.
QList<int> list;
for (int i = 0; i < iterations; ++i)
list.append(i);
// Create a progress dialog.
QProgressDialog dialog;
dialog.setLabelText(QString("Progressing using %1 thread(s)...").arg(QThread::idealThreadCount()));
// Create a QFutureWatcher and connect signals and slots.
QFutureWatcher<void> futureWatcher;
QObject::connect(&futureWatcher, &QFutureWatcher<void>::finished, &dialog, &QProgressDialog::reset);
QObject::connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher<void>::cancel);
QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressRangeChanged, &dialog, &QProgressDialog::setRange);
QObject::connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, &QProgressDialog::setValue);
// Our function to compute
std::function<void(int&)> spin = [](int &iteration) {
const int work = 1000 * 1000 * 40;
volatile int v = 0;
for (int j = 0; j < work; ++j)
++v;
qDebug() << "iteration" << iteration << "in thread" << QThread::currentThreadId();
};
// Start the computation.
futureWatcher.setFuture(QtConcurrent::map(list, spin));
// Display the dialog and start the event loop.
dialog.exec();
futureWatcher.waitForFinished();
// Query the future to check if was canceled.
qDebug() << "Canceled?" << futureWatcher.future().isCanceled();
}

View File

@ -1,7 +0,0 @@
QT += concurrent widgets
CONFIG += console
SOURCES += main.cpp
target.path = $$[QT_INSTALL_EXAMPLES]/qtconcurrent/progressdialog
INSTALLS += target

View File

@ -2,12 +2,12 @@ requires(qtHaveModule(concurrent))
TEMPLATE = subdirs
SUBDIRS = imagescaling \
progressdialog \
primecounter \
wordcount
!qtHaveModule(widgets) {
SUBDIRS -= \
imagescaling \
progressdialog \
primecounter \
wordcount
}