summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp34
-rw-r--r--src/corelib/thread/qfuture.h20
-rw-r--r--src/corelib/thread/qfuture.qdoc35
-rw-r--r--src/corelib/thread/qfuture_impl.h53
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp267
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"