From 68de38ded1c0e5387ae29aacaee50ba5dacfc59a Mon Sep 17 00:00:00 2001 From: Andrei Golubev Date: Thu, 11 Jun 2020 17:16:54 +0300 Subject: 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 --- src/corelib/doc/qtcore.qdocconf | 3 +- src/corelib/thread/qpromise.h | 5 + src/corelib/thread/qpromise.qdoc | 231 +++++++++++++++++++++ .../corelib/thread/qpromise/snippet_qpromise.cpp | 200 ++++++++++++++++++ .../auto/corelib/thread/qpromise/tst_qpromise.cpp | 22 ++ 5 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 src/corelib/thread/qpromise.qdoc create mode 100644 tests/auto/corelib/thread/qpromise/snippet_qpromise.cpp 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 d = QFutureInterface(); 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 QPromise::QPromise() + + Constructs a QPromise with a default state. +*/ + +/*! \fn template QPromise::QPromise(QPromise &&other) + + Move constructs a new QPromise from \a other. + + \sa operator=() +*/ + +/*! \fn template QPromise &QPromise::operator=(QPromise &&other) + + Move assigns \a other to this promise and returns a reference to this + promise. +*/ + +/*! \fn template QPromise::~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 QFuture QPromise::future() const + + Returns a future associated with this promise. +*/ + +/*! \fn template void QPromise::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 void QPromise::addResult(T &&result, int index = -1) + + \overload +*/ + +/*! \fn template void QPromise::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 void QPromise::setException(std::exception_ptr e) + + \overload +*/ + +/*! \fn template void QPromise::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 void QPromise::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 void QPromise::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 bool QPromise::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 void QPromise::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 void QPromise::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 void QPromise::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 +#include + +#include +#include +#include +#include +#include + +class snippet_QPromise +{ +public: + static void basicExample(); + static void multithreadExample(); + static void suspendExample(); +}; + +void snippet_QPromise::basicExample() +{ +//! [basic] + QPromise promise; + QFuture 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 thread(QThread::create([] (QPromise 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> sharedPromise(new QPromise()); + QFuture future = sharedPromise->future(); + + // ... +//! [multithread_init] + + sharedPromise->reportStarted(); + +//! [multithread_main] + // here, QPromise is shared between threads via a smart pointer + QPointer threads[] = { + QPointer(QThread::create([] (auto sharedPromise) { + sharedPromise->addResult(0, 0); // adds value 0 by index 0 + }, sharedPromise)), + QPointer(QThread::create([] (auto sharedPromise) { + sharedPromise->addResult(-1, 1); // adds value -1 by index 1 + }, sharedPromise)), + QPointer(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 promise; + QFuture future = promise.future(); + + promise.reportStarted(); + // Start a computation thread that supports suspension and cancellation + QPointer thread(QThread::create([] (QPromise 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 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" -- cgit v1.2.3