Document QPromise API

Documented QPromise. Added snippets under auto tests
to ensure they are compiled and run in CI.

Task-number: QTBUG-81586
Change-Id: I20084e38f9d2f6fc8540f95ee03ec3d2827177e8
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
Andrei Golubev 2020-06-11 17:16:54 +03:00
parent b0f445a152
commit 68de38ded1
5 changed files with 460 additions and 1 deletions

View File

@ -37,7 +37,8 @@ exampledirs += \
snippets \
../../../examples/corelib \
../../../examples/network/dnslookup \
../../../examples/widgets/tools
../../../examples/widgets/tools \
../../../tests/auto/corelib/thread/qpromise/
imagedirs += images

View File

@ -110,6 +110,11 @@ public:
d.setProgressValueAndText(progressValue, progressText);
}
#if defined(Q_CLANG_QDOC) // documentation-only simplified signatures
void addResult(const T &result, int index = -1) { }
void addResult(T &&result, int index = -1) { }
#endif
private:
mutable QFutureInterface<T> d = QFutureInterface<T>();

View File

@ -0,0 +1,231 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/
/*! \class QPromise
\inmodule QtCore
\threadsafe
\brief The QPromise class provides a way to store computation results to be accessed by QFuture.
\since 6.0
\ingroup thread
QPromise provides a simple way to communicate progress and results of the
user-defined computation to QFuture in an asynchronous fashion. For the
communication to work, QFuture must be constructed by QPromise.
You can use QPromise based workloads as an alternative to \l {Qt Concurrent}
framework when fine-grained control is needed or high-level communication
primitive to accompany QFuture is sufficient.
The simplest case of promise and future collaboration would be a single
result communication:
\snippet snippet_qpromise.cpp basic
By design, QPromise is a move-only object. This behavior helps to ensure
that whenever the promise is destroyed, the associated future object is
notified and will not wait forever for the results to become available.
However, this is inconvenient if one wants to use the same promise to report
results from different threads. There is no specific way to do that at the
moment, but known mechanisms exist, such as the use of smart pointers or raw
pointers/references. QSharedPointer is a good default choice if you want to
copy your promise and use it in multiple places simultaneously. Raw pointers
or references are, in a sense, easier, and probably perform better (since
there is no need to do a resource management) but may lead to dangling.
Here is an example of how a promise can be used in multiple threads:
\snippet snippet_qpromise.cpp multithread_init
\codeline
\snippet snippet_qpromise.cpp multithread_main
\sa QFuture
*/
/*! \fn template <typename T> QPromise<T>::QPromise()
Constructs a QPromise with a default state.
*/
/*! \fn template <typename T> QPromise<T>::QPromise(QPromise<T> &&other)
Move constructs a new QPromise from \a other.
\sa operator=()
*/
/*! \fn template <typename T> QPromise<T> &QPromise<T>::operator=(QPromise<T> &&other)
Move assigns \a other to this promise and returns a reference to this
promise.
*/
/*! \fn template <typename T> QPromise<T>::~QPromise()
Destroys the promise.
\note The promise implicitly transitions to a cancelled state on destruction
unless reportFinished() is called beforehand by the user.
*/
/*! \fn template <typename T> QFuture<T> QPromise<T>::future() const
Returns a future associated with this promise.
*/
/*! \fn template <typename T> void QPromise<T>::addResult(const T &result, int index = -1)
Adds \a result to the internal result collection at \a index position. If
index is unspecified, \a result is added to the end of the collection.
You can get a result at a specific index by calling QFuture::resultAt().
\note It is possible to specify an arbitrary index and request result at
that index. However, some QFuture methods operate with continuous results.
For instance, iterative approaches that use QFuture::resultCount() or
QFuture::const_iterator. In order to get all available results without
thinking if there are index gaps or not, use QFuture::results().
*/
/*! \fn template <typename T> void QPromise<T>::addResult(T &&result, int index = -1)
\overload
*/
/*! \fn template<typename T> void QPromise<T>::setException(const QException &e)
Sets exception \a e to be the result of the computation.
\note You can set at most one exception throughout the computation
execution.
\note This method must not be used after QFuture::cancel() or
reportFinished().
\sa isCanceled()
*/
/*! \fn template<typename T> void QPromise<T>::setException(std::exception_ptr e)
\overload
*/
/*! \fn template<typename T> void QPromise<T>::reportStarted()
Reports that the computation is started. Calling this method is important to
state the beginning of the computation as QFuture methods rely on this
information.
\note Extra attention is required when reportStarted() is called from a
newly created thread. In such case, the call might naturally be delayed due
to the implementation details of the thread scheduling.
\sa QFuture::isStarted(), QFuture::waitForFinished(), reportFinished()
*/
/*! \fn template<typename T> void QPromise<T>::reportFinished()
Reports that the computation is finished. Once finished, no new results will
be added when calling addResult(). This method accompanies reportStarted().
\sa QFuture::isFinished(), QFuture::waitForFinished(), reportStarted()
*/
/*! \fn template<typename T> void QPromise<T>::suspendIfRequested()
Conditionally suspends current thread of execution and waits until resumed
or cancelled by the corresponding methods of QFuture. This method does not
block unless the computation is requested to be suspended by
QFuture::suspend() or another related method. If you want to check that the
execution has been suspended, use QFuture::isSuspended().
\note When using the same promise in multiple threads,
QFuture::isSuspended() becomes \c true as soon as at least one thread with
the promise suspends.
The following code snippets show the usage of suspension mechanism:
\snippet snippet_qpromise.cpp suspend_start
QFuture::suspend() requests the associated promise to suspend:
\snippet snippet_qpromise.cpp suspend_suspend
After QFuture::isSuspended() becomes \c true, you can get intermediate
results:
\snippet snippet_qpromise.cpp suspend_intermediateResults
When suspended, you can resume or cancel the awaiting computation:
\snippet snippet_qpromise.cpp suspend_end
\sa QFuture::resume(), QFuture::cancel(), QFuture::setSuspended(),
QFuture::toggleSuspended()
*/
/*! \fn template<typename T> bool QPromise<T>::isCanceled() const
Returns whether the computation has been cancelled with the
QFuture::cancel() function. The returned value \c true indicates that the
computation should be finished and reportFinished() called.
\note After cancellation, results currently available may still be accessed
by a future, but new results will not be added when calling addResult().
*/
/*! \fn template<typename T> void QPromise<T>::setProgressRange(int minimum, int maximum)
Sets the progress range of the computation to be between \a minimum and \a
maximum.
\sa QFuture::progressMinimum(), QFuture::progressMaximum()
*/
/*! \fn template<typename T> void QPromise<T>::setProgressValue(int progressValue)
Sets the progress value of the computation to \a progressValue. It is
possible to only increment the progress value. This is a convenience method
for calling setProgressValueAndText(progressValue, QString()).
\sa QFuture::progressValue()
*/
/*! \fn template<typename T> void QPromise<T>::setProgressValueAndText(int progressValue, const QString &progressText)
Sets the progress value and the progress text of the computation to \a
progressValue and \a progressText respectively. It is possible to only
increment the progress value.
\note This function has no effect if the promise is in cancelled or finished
state.
\sa QFuture::progressValue(), QFuture::progressText(), QFuture::cancel(),
reportFinished()
*/

View File

@ -0,0 +1,200 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Note: this file is published under a license that is different from a default
// test sources license. This is intentional to comply with default
// snippet license.
#include <QCoreApplication>
#include <QtTest/QtTest>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qpromise.h>
#include <qpointer.h>
#include <qsharedpointer.h>
class snippet_QPromise
{
public:
static void basicExample();
static void multithreadExample();
static void suspendExample();
};
void snippet_QPromise::basicExample()
{
//! [basic]
QPromise<int> promise;
QFuture<int> future = promise.future();
// Note: calling reportStarted() prior to thread creation to enforce order
// of calls: first promise.reportStarted() then future.waitForFinished()
promise.reportStarted(); // notifies QFuture that the computation is started
QPointer<QThread> thread(QThread::create([] (QPromise<int> promise) {
promise.addResult(42);
promise.reportFinished(); // notifies QFuture that the computation is finished
}, std::move(promise)));
thread->start();
future.waitForFinished(); // blocks until QPromise::reportFinished is called
future.result(); // returns 42
//! [basic]
QCOMPARE(future.result(), 42);
thread->wait();
}
void snippet_QPromise::multithreadExample()
{
//! [multithread_init]
QSharedPointer<QPromise<int>> sharedPromise(new QPromise<int>());
QFuture<int> future = sharedPromise->future();
// ...
//! [multithread_init]
sharedPromise->reportStarted();
//! [multithread_main]
// here, QPromise is shared between threads via a smart pointer
QPointer<QThread> threads[] = {
QPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(0, 0); // adds value 0 by index 0
}, sharedPromise)),
QPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(-1, 1); // adds value -1 by index 1
}, sharedPromise)),
QPointer<QThread>(QThread::create([] (auto sharedPromise) {
sharedPromise->addResult(-2, 2); // adds value -2 by index 2
}, sharedPromise)),
// ...
};
// start all threads
for (auto& t : threads)
t->start();
// ...
future.resultAt(0); // waits until result at index 0 becomes available. returns value 0
future.resultAt(1); // waits until result at index 1 becomes available. returns value -1
future.resultAt(2); // waits until result at index 2 becomes available. returns value -2
//! [multithread_main]
QCOMPARE(future.resultAt(0), 0);
QCOMPARE(future.resultAt(1), -1);
QCOMPARE(future.resultAt(2), -2);
for (auto& t : threads)
t->wait();
sharedPromise->reportFinished();
}
void snippet_QPromise::suspendExample()
{
//! [suspend_start]
// Create promise and future
QPromise<int> promise;
QFuture<int> future = promise.future();
promise.reportStarted();
// Start a computation thread that supports suspension and cancellation
QPointer<QThread> thread(QThread::create([] (QPromise<int> promise) {
for (int i = 0; i < 100; ++i) {
promise.addResult(i);
promise.suspendIfRequested(); // support suspension
if (promise.isCanceled()) // support cancellation
break;
}
promise.reportFinished();
}, std::move(promise)));
thread->start();
//! [suspend_start]
//! [suspend_suspend]
future.suspend();
//! [suspend_suspend]
// wait in calling thread until future.isSuspended() becomes true or do
// something meanwhile
while (!future.isSuspended()) {
QThread::msleep(50);
}
//! [suspend_intermediateResults]
future.resultCount(); // returns some number between 0 and 100
for (int i = 0; i < future.resultCount(); ++i) {
// process results available before suspension
}
//! [suspend_intermediateResults]
// at least one result is available due to the logic inside a thread
QVERIFY(future.resultCount() > 0);
QVERIFY(future.resultCount() <= 100);
for (int i = 0; i < future.resultCount(); ++i) {
QCOMPARE(future.resultAt(i), i);
}
//! [suspend_end]
future.resume(); // resumes computation, this call will unblock the promise
// alternatively, call future.cancel() to stop the computation
future.waitForFinished();
future.results(); // returns all computation results - array of values from 0 to 99
//! [suspend_end]
thread->wait();
QCOMPARE(future.resultCount(), 100);
QVector<int> expected(100);
std::iota(expected.begin(), expected.end(), 0);
QCOMPARE(future.results(), expected);
}

View File

@ -68,6 +68,11 @@ private slots:
void cancelWhenMoved();
void waitUntilResumed();
void waitUntilCanceled();
// snippets (external):
void snippet_basicExample();
void snippet_multithreadExample();
void snippet_suspendExample();
};
struct TrivialType { int field = 0; };
@ -573,5 +578,22 @@ void tst_QPromise::waitUntilCanceled()
QCOMPARE(f.resultCount(), 0);
}
// Below is a quick and dirty hack to make snippets a part of a test suite
#include "snippet_qpromise.cpp"
void tst_QPromise::snippet_basicExample()
{
snippet_QPromise::basicExample();
}
void tst_QPromise::snippet_multithreadExample()
{
snippet_QPromise::multithreadExample();
}
void tst_QPromise::snippet_suspendExample()
{
snippet_QPromise::suspendExample();
}
QTEST_MAIN(tst_QPromise)
#include "tst_qpromise.moc"