diff options
-rw-r--r-- | src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp | 34 | ||||
-rw-r--r-- | src/corelib/thread/qfuture.h | 20 | ||||
-rw-r--r-- | src/corelib/thread/qfuture.qdoc | 35 | ||||
-rw-r--r-- | src/corelib/thread/qfuture_impl.h | 53 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 267 |
5 files changed, 409 insertions, 0 deletions
diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp index ffb78dc4c8..9ece90553a 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -398,3 +398,37 @@ QtFuture::whenAny(intFuture, stringFuture, voidFuture).then([](const FuturesVari ... }); //! [27] + +//! [28] + +QFuture<QFuture<int>> outerFuture = ...; +QFuture<int> unwrappedFuture = outerFuture.unwrap(); + +//! [28] + +//! [29] + +auto downloadImages = [] (const QUrl &url) { + QList<QImage> images; + ... + return images; +}; + +auto processImages = [](const QList<QImage> &images) { + return QtConcurrent::mappedReduced(images, scale, reduceImages); +} + +auto show = [](const QImage &image) { ... }; + +auto future = QtConcurrent::run(downloadImages, url) + .then(processImages) + .unwrap() + .then(show); +//! [29] + +//! [30] + +QFuture<QFuture<QFuture<int>>>> outerFuture; +QFuture<int> unwrappedFuture = outerFuture.unwrap(); + +//! [30] diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h index 7aed38be16..c450221769 100644 --- a/src/corelib/thread/qfuture.h +++ b/src/corelib/thread/qfuture.h @@ -191,6 +191,14 @@ QT_WARNING_POP template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>> QFuture<T> onCanceled(QObject *context, Function &&handler); +#if !defined(Q_CLANG_QDOC) + template<class U = T, typename = std::enable_if_t<QtPrivate::isQFutureV<U>>> + auto unwrap(); +#else + template<class U> + QFuture<U> unwrap(); +#endif + class const_iterator { public: @@ -317,6 +325,8 @@ private: template<typename ResultType> friend struct QtPrivate::WhenAnyContext; + friend struct QtPrivate::UnwrapHandler; + using QFuturePrivate = std::conditional_t<std::is_same_v<T, void>, QFutureInterfaceBase, QFutureInterface<T>>; @@ -431,6 +441,16 @@ QFuture<T> QFuture<T>::onCanceled(QObject *context, Function &&handler) return promise.future(); } +template<class T> +template<class U, typename> +auto QFuture<T>::unwrap() +{ + if constexpr (QtPrivate::isQFutureV<typename QtPrivate::Future<T>::type>) + return QtPrivate::UnwrapHandler::unwrapImpl(this).unwrap(); + else + return QtPrivate::UnwrapHandler::unwrapImpl(this); +} + inline QFuture<void> QFutureInterface<void>::future() { return QFuture<void>(this); diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index d52db1c481..bc710bc42f 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -1357,6 +1357,41 @@ \sa then(), onFailed() */ +/*! \fn template<class T> template<class U> QFuture<U> QFuture<T>::unwrap() + + \since 6.4 + + Unwraps the inner future from this \c QFuture<T>, where \c T is a future + of type \c QFuture<U>, i.e. this future has type of \c QFuture<QFuture<U>>. + For example: + + \snippet code/src_corelib_thread_qfuture.cpp 28 + + \c unwrappedFuture will be fulfilled as soon as the inner future nested + inside the \c outerFuture is fulfilled, with the same result or exception + and in the same thread that reports the inner future as finished. If the + inner future is canceled, \c unwrappedFuture will also be canceled. + + This is especially useful when chaining multiple computations, and one of + them returns a \c QFuture as its result type. For example, let's say we + want to download multiple images from an URL, scale the images, and reduce + them to a single image using QtConcurrent::mappedReduced(). We could write + something like: + + \snippet code/src_corelib_thread_qfuture.cpp 29 + + Here \c QtConcurrent::mappedReduced() returns a \c QFuture<QImage>, so + \c .then(processImages) returns a \c QFuture<QFuture<QImage>>. Since + \c show() takes a \c QImage as argument, the result of \c .then(processImages) + can't be passed to it directly. We need to call \c .unwrap(), that will + get the result of the inner future when it's ready and pass it to the next + continuation. + + In case of multiple nesting, \c .unwrap() goes down to the innermost level: + + \snippet code/src_corelib_thread_qfuture.cpp 30 +*/ + /*! \fn template<typename OutputSequence, typename InputIt> QFuture<OutputSequence> QtFuture::whenAll(InputIt first, InputIt last) \since 6.3 diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index e4b0ea6289..5a70659c1d 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -867,6 +867,59 @@ public: } }; +struct UnwrapHandler +{ + template<class T> + static auto unwrapImpl(T *outer) + { + Q_ASSERT(outer); + + using ResultType = typename QtPrivate::Future<std::decay_t<T>>::type; + using NestedType = typename QtPrivate::Future<ResultType>::type; + QFutureInterface<NestedType> promise(QFutureInterfaceBase::State::Pending); + + outer->then([promise](const QFuture<ResultType> &outerFuture) mutable { + // We use the .then([](QFuture<ResultType> outerFuture) {...}) version + // (where outerFuture == *outer), to propagate the exception if the + // outer future has failed. + Q_ASSERT(outerFuture.isFinished()); +#ifndef QT_NO_EXCEPTIONS + if (outerFuture.d.hasException()) { + promise.reportStarted(); + promise.reportException(outerFuture.d.exceptionStore().exception()); + promise.reportFinished(); + return; + } +#endif + + promise.reportStarted(); + ResultType nestedFuture = outerFuture.result(); + + nestedFuture.then([promise] (const QFuture<NestedType> &nested) mutable { +#ifndef QT_NO_EXCEPTIONS + if (nested.d.hasException()) { + promise.reportException(nested.d.exceptionStore().exception()); + } else +#endif + { + if constexpr (!std::is_void_v<NestedType>) + promise.reportResults(nested.results()); + } + promise.reportFinished(); + }).onCanceled([promise] () mutable { + promise.reportCanceled(); + promise.reportFinished(); + }); + }).onCanceled([promise]() mutable { + // propagate the cancellation of the outer future + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + }); + return promise.future(); + } +}; + } // namespace QtPrivate namespace QtFuture { diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 428cc7b795..f1b6f5af4d 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -247,6 +247,8 @@ private slots: void continuationsDontLeak(); + void unwrap(); + private: using size_type = std::vector<int>::size_type; @@ -4664,5 +4666,270 @@ void tst_QFuture::continuationsDontLeak() QCOMPARE(InstanceCounter::count, 0); } +void tst_QFuture::unwrap() +{ + // The nested future succeeds + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // The nested future succeeds with multiple results + { + QPromise<int> p; + QFuture<QFuture<int>> f = p.future().then([] (int value) { + QPromise<int> nested; + nested.start(); + nested.addResult(++value); + nested.addResult(++value); + nested.addResult(++value); + nested.finish(); + return nested.future(); + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.results(), QList<int>() << 43 << 44 << 45); + } + + // The chain is canceled, check that unwrap() propagates the cancellation. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.future().cancel(); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The chain has an exception, check that unwrap() propagates it. + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.setException(QException()); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#endif // QT_NO_EXCEPTIONS + + // The nested future is canceled + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + return value + 1; + }); + nested.cancel(); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onCanceled([] { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } + +#ifndef QT_NO_EXCEPTIONS + // The nested future fails with an exception + { + QPromise<int> p; + QFuture<int> f = p.future().then([] (int value) { + QFuture<int> nested = QtConcurrent::run([value] { + throw QException(); + return value + 1; + }); + return nested; + }).unwrap().then([] (int result) { + return result; + }).onFailed([] (QException &) { + return -1; + }); + + p.start(); + p.addResult(42); + p.finish(); + + f.waitForFinished(); + + QVERIFY(f.isStarted()); + QVERIFY(f.isFinished()); + QCOMPARE(f.result(), -1); + } +#endif // QT_NO_EXCEPTIONS + + // Check that continuations are called in the right order + { + QPromise<void> p; + + std::atomic<bool> firstThenInvoked = false; + std::atomic<bool> secondThenInvoked = false; + std::atomic<bool> nestedThenInvoked = false; + auto f = p.future().then([&] { + if (!firstThenInvoked && !secondThenInvoked && !nestedThenInvoked) + firstThenInvoked = true; + QFuture<void> nested = QtConcurrent::run([&] { + QVERIFY(firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + nestedThenInvoked = true; + }); + return nested; + }).unwrap().then([&] { + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(!secondThenInvoked); + secondThenInvoked = true; + }); + + QVERIFY(!firstThenInvoked); + QVERIFY(!nestedThenInvoked); + QVERIFY(!secondThenInvoked); + + p.start(); + p.finish(); + + f.waitForFinished(); + + if (QTest::currentTestFailed()) + return; + + QVERIFY(firstThenInvoked); + QVERIFY(nestedThenInvoked); + QVERIFY(secondThenInvoked); + } + + // Unwrap multiple nested futures + { + QPromise<int> p; + QFuture<QFuture<QFuture<int>>> f = p.future().then([] (int value) { + QFuture<QFuture<int>> nested = QtConcurrent::run([value] { + QFuture<int> doubleNested = QtConcurrent::run([value] { + return value + 1; + }); + return doubleNested; + }); + return nested; + }); + + QFuture<int> unwrapped = f.unwrap(); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.addResult(42); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QCOMPARE(unwrapped.result(), 43); + } + + // Unwrap multiple nested void futures + { + QPromise<void> p; + std::atomic<bool> nestedInvoked = false; + std::atomic<bool> doubleNestedInvoked = false; + QFuture<QFuture<QFuture<void>>> f = p.future().then([&] { + QFuture<QFuture<void>> nested = QtConcurrent::run([&] { + QFuture<void> doubleNested = QtConcurrent::run([&] { + doubleNestedInvoked = true; + }); + nestedInvoked = true; + return doubleNested; + }); + return nested; + }); + + QFuture<void> unwrapped = f.unwrap(); + QVERIFY(!nestedInvoked); + QVERIFY(!doubleNestedInvoked); + QVERIFY(!unwrapped.isStarted()); + QVERIFY(!unwrapped.isFinished()); + + p.start(); + p.finish(); + + unwrapped.waitForFinished(); + + QVERIFY(unwrapped.isStarted()); + QVERIFY(unwrapped.isFinished()); + QVERIFY(nestedInvoked); + QVERIFY(doubleNestedInvoked); + } +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc" |