Add support for unwrapping QFuture<QFuture<T>>

Added QFuture::unwrap() for unwrapping the future nested inside
QFuture<QFuture<T>>. QTBUG-86725 suggests doing the unwrapping
automatically inside .then(), but this will change the return type
of .then() that used to return QFuture<QFuture<T>> and might cause
SC breaks. Apart from that, QFuture::unwrap() might be helpful in
general, for asynchronous computations that return a nested QFuture.

[ChangeLog][QtCore][QFuture] Added QFuture::unwrap() for unwrapping the
future nested inside QFuture<QFuture<T>>.

Task-number: QTBUG-86725
Change-Id: I8886743aca261dca46f62d9dfcaead4a141d3dc4
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Sona Kurazyan 2022-04-06 13:03:12 +02:00
parent f08fd3c055
commit 9eb090d683
5 changed files with 409 additions and 0 deletions

View File

@ -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]

View File

@ -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);

View File

@ -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

View File

@ -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 {

View File

@ -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"