summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2020-06-11 17:16:54 +0300
committerAndrei Golubev <andrei.golubev@qt.io>2020-06-18 18:28:41 +0300
commit68de38ded1c0e5387ae29aacaee50ba5dacfc59a (patch)
tree4bef76d45efcf52fcbebb862c353776b3b93cbd7
parentb0f445a1526fa43563522d865c5ad1546201003c (diff)
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>
-rw-r--r--src/corelib/doc/qtcore.qdocconf3
-rw-r--r--src/corelib/thread/qpromise.h5
-rw-r--r--src/corelib/thread/qpromise.qdoc231
-rw-r--r--tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp200
-rw-r--r--tests/auto/corelib/thread/qpromise/tst_qpromise.cpp22
5 files changed, 460 insertions, 1 deletions
diff --git a/src/corelib/doc/qtcore.qdocconf b/src/corelib/doc/qtcore.qdocconf
index 2b9adabc3a..e930008e47 100644
--- a/src/corelib/doc/qtcore.qdocconf
+++ b/src/corelib/doc/qtcore.qdocconf
@@ -37,7 +37,8 @@ exampledirs += \
snippets \
../../../examples/corelib \
../../../examples/network/dnslookup \
- ../../../examples/widgets/tools
+ ../../../examples/widgets/tools \
+ ../../../tests/auto/corelib/thread/qpromise/
imagedirs += images
diff --git a/src/corelib/thread/qpromise.h b/src/corelib/thread/qpromise.h
index 15aea16f29..9cd9238381 100644
--- a/src/corelib/thread/qpromise.h
+++ b/src/corelib/thread/qpromise.h
@@ -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>();
diff --git a/src/corelib/thread/qpromise.qdoc b/src/corelib/thread/qpromise.qdoc
new file mode 100644
index 0000000000..e78e16bdd6
--- /dev/null
+++ b/src/corelib/thread/qpromise.qdoc
@@ -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()
+*/
diff --git a/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp b/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp
new file mode 100644
index 0000000000..4825ee80d5
--- /dev/null
+++ b/tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp
@@ -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);
+}
diff --git a/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp b/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp
index 1e5864e59e..02c5b6a8bd 100644
--- a/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp
+++ b/tests/auto/corelib/thread/qpromise/tst_qpromise.cpp
@@ -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"