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:
parent
f08fd3c055
commit
9eb090d683
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user