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:
parent
b0f445a152
commit
68de38ded1
@ -37,7 +37,8 @@ exampledirs += \
|
||||
snippets \
|
||||
../../../examples/corelib \
|
||||
../../../examples/network/dnslookup \
|
||||
../../../examples/widgets/tools
|
||||
../../../examples/widgets/tools \
|
||||
../../../tests/auto/corelib/thread/qpromise/
|
||||
|
||||
imagedirs += images
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
231
src/corelib/thread/qpromise.qdoc
Normal file
231
src/corelib/thread/qpromise.qdoc
Normal 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()
|
||||
*/
|
200
tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp
Normal file
200
tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp
Normal 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);
|
||||
}
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user