Add support for combining multiple QFutures

[ChangeLog][QtCore] Added QtFuture::whenAll() and QtFuture::whenAny()
functions, returning a QFuture that becomes ready when all or any of the
supplied futures complete.

Task-number: QTBUG-86714
Change-Id: I2bb7dbb4cdc4f79a7a4fd494142df6a0f93a2b39
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Sona Kurazyan 2021-11-04 17:01:54 +01:00
parent 3b49aa72fe
commit 102f7d31c4
5 changed files with 1022 additions and 0 deletions

View File

@ -311,3 +311,90 @@ auto resultFuture = testFuture.then([](int res) {
return -1;
});
//! [21]
//! [22]
QList<QFuture<int>> inputFutures {...};
// whenAll has type QFuture<QList<QFuture<int>>>
auto whenAll = QtFuture::whenAll(inputFutures.begin(), inputFutures.end());
// whenAllVector has type QFuture<std::vector<QFuture<int>>>
auto whenAllVector =
QtFuture::whenAll<std::vector<QFuture<int>>>(inputFutures.begin(), inputFutures.end());
//! [22]
//! [23]
QList<QFuture<int>> inputFutures {...};
QtFuture::whenAll(inputFutures.begin(), inputFutures.end())
.then([](const QList<QFuture<int>> &results) {
for (auto future : results) {
if (future.isCanceled())
// handle the cancellation (possibly due to an exception)
else
// do something with the result
}
});
//! [23]
//! [24]
QFuture<int> intFuture = ...;
QFuture<QString> stringFuture = ...;
QFuture<void> voidFuture = ...;
using FuturesVariant = std::variant<QFuture<int>, QFuture<QString>, QFuture<void>>;
// whenAll has type QFuture<QList<FuturesVariant>>
auto whenAll = QtFuture::whenAll(intFuture, stringFuture, voidFuture);
// whenAllVector has type QFuture<std::vector<FuturesVariant>>
auto whenAllVector =
QtFuture::whenAll<std::vector<FuturesVariant>>(intFuture, stringFuture, voidFuture);
//! [24]
//! [25]
QFuture<int> intFuture = ...;
QFuture<QString> stringFuture = ...;
QFuture<void> voidFuture = ...;
using FuturesVariant = std::variant<QFuture<int>, QFuture<QString>, QFuture<void>>;
QtFuture::whenAll(intFuture, stringFuture, voidFuture)
.then([](const QList<FuturesVariant> &results) {
...
for (auto result : results)
{
// assuming handleResult() is overloaded based on the QFuture type
std::visit([](auto &&future) { handleResult(future); }, result);
}
...
});
//! [25]
//! [26]
QList<QFuture<int>> inputFutures = ...;
QtFuture::whenAny(inputFutures.begin(), inputFutures.end())
.then([](const QtFuture::WhenAnyResult<int> &result) {
qsizetype index = result.index;
QFuture<int> future = result.future;
// ...
});
//! [26]
//! [27]
QFuture<int> intFuture = ...;
QFuture<QString> stringFuture = ...;
QFuture<void> voidFuture = ...;
using FuturesVariant = std::variant<QFuture<int>, QFuture<QString>, QFuture<void>>;
QtFuture::whenAny(intFuture, stringFuture, voidFuture).then([](const FuturesVariant &result) {
...
// assuming handleResult() is overloaded based on the QFuture type
std::visit([](auto &&future) { handleResult(future); }, result);
...
});
//! [27]

View File

@ -314,6 +314,9 @@ private:
friend class QtPrivate::FailureHandler;
#endif
template<typename ResultType>
friend struct QtPrivate::WhenAnyContext;
using QFuturePrivate =
std::conditional_t<std::is_same_v<T, void>, QFutureInterfaceBase, QFutureInterface<T>>;
@ -456,6 +459,87 @@ struct MetaTypeQFutureHelper<QFuture<T>>
} // namespace QtPrivate
namespace QtFuture {
#ifndef Q_CLANG_QDOC
template<typename OutputSequence, typename InputIt,
typename ValueType = typename std::iterator_traits<InputIt>::value_type,
std::enable_if_t<std::conjunction_v<QtPrivate::IsForwardIterable<InputIt>,
QtPrivate::IsRandomAccessible<OutputSequence>,
QtPrivate::isQFuture<ValueType>>,
int> = 0>
QFuture<OutputSequence> whenAll(InputIt first, InputIt last)
{
return QtPrivate::whenAllImpl<OutputSequence, InputIt, ValueType>(first, last);
}
template<typename InputIt, typename ValueType = typename std::iterator_traits<InputIt>::value_type,
std::enable_if_t<std::conjunction_v<QtPrivate::IsForwardIterable<InputIt>,
QtPrivate::isQFuture<ValueType>>,
int> = 0>
QFuture<QList<ValueType>> whenAll(InputIt first, InputIt last)
{
return QtPrivate::whenAllImpl<QList<ValueType>, InputIt, ValueType>(first, last);
}
template<typename OutputSequence, typename... Futures,
std::enable_if_t<std::conjunction_v<QtPrivate::IsRandomAccessible<OutputSequence>,
QtPrivate::NotEmpty<Futures...>,
QtPrivate::isQFuture<std::decay_t<Futures>>...>,
int> = 0>
QFuture<OutputSequence> whenAll(Futures &&... futures)
{
return QtPrivate::whenAllImpl<OutputSequence, Futures...>(std::forward<Futures>(futures)...);
}
template<typename... Futures,
std::enable_if_t<std::conjunction_v<QtPrivate::NotEmpty<Futures...>,
QtPrivate::isQFuture<std::decay_t<Futures>>...>,
int> = 0>
QFuture<QList<std::variant<std::decay_t<Futures>...>>> whenAll(Futures &&... futures)
{
return QtPrivate::whenAllImpl<QList<std::variant<std::decay_t<Futures>...>>, Futures...>(
std::forward<Futures>(futures)...);
}
template<typename InputIt, typename ValueType = typename std::iterator_traits<InputIt>::value_type,
std::enable_if_t<std::conjunction_v<QtPrivate::IsForwardIterable<InputIt>,
QtPrivate::isQFuture<ValueType>>,
int> = 0>
QFuture<WhenAnyResult<typename QtPrivate::Future<ValueType>::type>> whenAny(InputIt first,
InputIt last)
{
return QtPrivate::whenAnyImpl<InputIt, ValueType>(first, last);
}
template<typename... Futures,
std::enable_if_t<std::conjunction_v<QtPrivate::NotEmpty<Futures...>,
QtPrivate::isQFuture<std::decay_t<Futures>>...>,
int> = 0>
QFuture<std::variant<std::decay_t<Futures>...>> whenAny(Futures &&... futures)
{
return QtPrivate::whenAnyImpl(std::forward<Futures>(futures)...);
}
#else
template<typename OutputSequence, typename InputIt>
QFuture<OutputSequence> whenAll(InputIt first, InputIt last);
template<typename OutputSequence, typename... Futures>
QFuture<OutputSequence> whenAll(Futures &&... futures);
template<typename T, typename InputIt>
QFuture<QtFuture::WhenAnyResult<T>> whenAny(InputIt first, InputIt last);
template<typename... Futures>
QFuture<std::variant<std::decay_t<Futures>...>> whenAny(Futures &&... futures);
#endif // Q_CLANG_QDOC
} // namespace QtFuture
Q_DECLARE_SEQUENTIAL_ITERATOR(Future)
QT_END_NAMESPACE

View File

@ -908,6 +908,38 @@
*/
/*!
\class QtFuture::WhenAnyResult
\inmodule QtCore
\ingroup thread
\brief QtFuture::WhenAnyResult is used to represent the result of QtFuture::whenAny().
\since 6.3
The \c {QtFuture::WhenAnyResult<T>} struct is used for packaging the copy and
the index of the first completed \c QFuture<T> in the sequence of futures
packaging type \c T that are passed to QtFuture::whenAny().
\sa QFuture, QtFuture::whenAny()
*/
/*!
\variable QtFuture::WhenAnyResult::index
The field contains the index of the first completed QFuture in the sequence
of futures passed to whenAny(). It has type \c qsizetype.
\sa QtFuture::whenAny()
*/
/*!
\variable QtFuture::WhenAnyResult::future
The field contains the copy of the first completed QFuture that packages type
\c T, where \c T is the type packaged by the futures passed to whenAny().
\sa QtFuture::whenAny()
*/
/*! \fn template<class Sender, class Signal> static QFuture<ArgsType<Signal>> QtFuture::connect(Sender *sender, Signal signal)
Creates and returns a QFuture which will become available when the \a sender emits
@ -1321,3 +1353,119 @@
\sa then(), onFailed()
*/
/*! \fn template<typename OutputSequence, typename InputIt> QFuture<OutputSequence> QtFuture::whenAll(InputIt first, InputIt last)
\since 6.3
Returns a new QFuture that succeeds when all futures from \a first to \a last
complete. \a first and \a last are iterators to a sequence of futures packaging
type \c T. \c OutputSequence is a sequence containing all completed futures
from \a first to \a last, appearing in the same order as in the input. If the
type of \c OutputSequence is not specified, the resulting futures will be
returned in a \c QList of \c QFuture<T>. For example:
\snippet code/src_corelib_thread_qfuture.cpp 22
\note The output sequence must support random access and support \c resize()
operation.
If \c first equals \c last, this function returns a ready QFuture that
contains an empty \c OutputSequence.
//! [whenAll]
The returned future always completes successfully after all the specified
futures complete. It doesn't matter if any of these futures completes with
error or is canceled. You can use \c .then() to process the completed futures
after the future returned by \c whenAll() succeeds:
//! [whenAll]
\snippet code/src_corelib_thread_qfuture.cpp 23
//! [whenAll-note]
\note If the input futures complete on different threads, the future returned
by this method will complete in the thread that the last future completes in.
Therefore, the continuations attached to the future returned by \c whenAll()
cannot always make assumptions about which thread they will be run on. Use the
overload of \c .then() that takes a context object if you want to control which
thread the continuations are invoked on.
//! [whenAll-note]
*/
/*! \fn template<typename OutputSequence, typename... Futures> QFuture<OutputSequence> QtFuture::whenAll(Futures &&... futures)
\since 6.3
Returns a new QFuture that succeeds when all \a futures packaging arbitrary
types complete. \c OutputSequence is a sequence of completed futures. The type
of its entries is \c std::variant<Futures...>. For each \c QFuture<T> passed to
\c whenAll(), the entry at the corresponding position in \c OutputSequence
will be a \c std::variant holding that \c QFuture<T>, in its completed state.
If the type of \c OutputSequence is not specified, the resulting futures will
be returned in a QList of \c std::variant<Futures...>. For example:
\snippet code/src_corelib_thread_qfuture.cpp 24
\note The output sequence should support random access and the \c resize()
operation.
\include qfuture.qdoc whenAll
\snippet code/src_corelib_thread_qfuture.cpp 25
\include qfuture.qdoc whenAll-note
*/
/*! \fn template<typename T, typename InputIt> QFuture<QtFuture::WhenAnyResult<T>> QtFuture::whenAny(InputIt first, InputIt last)
\since 6.3
Returns a new QFuture that succeeds when any of the futures from \a first to
\a last completes. \a first and \a last are iterators to a sequence of futures
packaging type \c T. The returned future packages a value of type
\c {QtFuture::WhenAnyResult<T>} which in turn packages the index of the
first completed \c QFuture and the \c QFuture itself. If \a first equals \a last,
this function returns a ready \c QFuture that has \c -1 for the \c index field in
the QtFuture::WhenAnyResult struct and a default-constructed \c QFuture<T> for
the \c future field. Note that a default-constructed QFuture is a completed
future in a cancelled state.
//! [whenAny]
The returned future always completes successfully after the first future
from the specified futures completes. It doesn't matter if the first future
completes with error or is canceled. You can use \c .then() to process the
result after the future returned by \c whenAny() succeeds:
//! [whenAny]
\snippet code/src_corelib_thread_qfuture.cpp 26
//! [whenAny-note]
\note If the input futures complete on different threads, the future returned
by this method will complete in the thread that the first future completes in.
Therefore, the continuations attached to the future returned by \c whenAny()
cannot always make assumptions about which thread they will be run on. Use the
overload of \c .then() that takes a context object if you want to control which
thread the continuations are invoked on.
//! [whenAny-note]
\sa QtFuture::WhenAnyResult
*/
/*! \fn template<typename... Futures> QFuture<std::variant<std::decay_t<Futures>...>> QtFuture::whenAny(Futures &&... futures)
\since 6.3
Returns a new QFuture that succeeds when any of the \a futures completes.
\a futures can package arbitrary types. The returned future packages the
value of type \c std::variant<Futures...> which in turn packages the first
completed QFuture from \a futures. You can use
\l {https://en.cppreference.com/w/cpp/utility/variant/index} {std::variant::index()}
to find out the index of the future in the sequence of \a futures that
finished first.
\include qfuture.qdoc whenAny
\snippet code/src_corelib_thread_qfuture.cpp 27
\include qfuture.qdoc whenAny-note
*/

View File

@ -65,7 +65,19 @@ template<class T>
class QPromise;
namespace QtFuture {
enum class Launch { Sync, Async, Inherit };
template<class T>
struct WhenAnyResult
{
qsizetype index = -1;
QFuture<T> future;
};
// Deduction guide
template<class T>
WhenAnyResult(qsizetype, const QFuture<T> &) -> WhenAnyResult<T>;
}
namespace QtPrivate {
@ -244,6 +256,40 @@ struct isTuple<std::tuple<T...>> : std::true_type
template<class T>
inline constexpr bool isTupleV = isTuple<T>::value;
template<class T>
inline constexpr bool isQFutureV = false;
template<class T>
inline constexpr bool isQFutureV<QFuture<T>> = true;
template<class T>
using isQFuture = std::bool_constant<isQFutureV<T>>;
template<class T>
struct Future
{
};
template<class T>
struct Future<QFuture<T>>
{
using type = T;
};
template<class... Args>
using NotEmpty = std::bool_constant<(sizeof...(Args) > 0)>;
template<class Sequence>
using IsRandomAccessible =
std::is_convertible<typename std::iterator_traits<std::decay_t<decltype(
std::begin(std::declval<Sequence>()))>>::iterator_category,
std::random_access_iterator_tag>;
template<class Iterator>
using IsForwardIterable =
std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category,
std::forward_iterator_tag>;
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation
{
@ -906,4 +952,147 @@ static QFuture<T> makeExceptionalFuture(const QException &exception)
} // namespace QtFuture
namespace QtPrivate {
template<typename ResultFutures>
struct WhenAllContext
{
using ValueType = typename ResultFutures::value_type;
WhenAllContext(qsizetype size) : count(size) {}
template<typename T = ValueType>
void checkForCompletion(qsizetype index, T &&future)
{
futures[index] = std::forward<T>(future);
Q_ASSERT(count > 0);
if (--count <= 0) {
promise.reportResult(futures);
promise.reportFinished();
}
}
QAtomicInteger<qsizetype> count;
QFutureInterface<ResultFutures> promise;
ResultFutures futures;
};
template<typename ResultType>
struct WhenAnyContext
{
using ValueType = ResultType;
template<typename T = ResultType, typename = EnableForNonVoid<T>>
void checkForCompletion(qsizetype, T &&result)
{
if (!ready.fetchAndStoreRelaxed(true)) {
promise.reportResult(std::forward<T>(result));
promise.reportFinished();
}
}
QAtomicInt ready = false;
QFutureInterface<ResultType> promise;
};
template<qsizetype Index, typename ContextType, typename... Ts>
void addCompletionHandlersImpl(const QSharedPointer<ContextType> &context,
const std::tuple<Ts...> &t)
{
auto future = std::get<Index>(t);
using ResultType = typename ContextType::ValueType;
future.then([context](const std::tuple_element_t<Index, std::tuple<Ts...>> &f) {
context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, f });
}).onCanceled([context, future]() {
context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, future });
});
if constexpr (Index != 0)
addCompletionHandlersImpl<Index - 1, ContextType, Ts...>(context, t);
}
template<typename ContextType, typename... Ts>
void addCompletionHandlers(const QSharedPointer<ContextType> &context, const std::tuple<Ts...> &t)
{
constexpr qsizetype size = std::tuple_size<std::tuple<Ts...>>::value;
addCompletionHandlersImpl<size - 1, ContextType, Ts...>(context, t);
}
template<typename OutputSequence, typename InputIt, typename ValueType>
QFuture<OutputSequence> whenAllImpl(InputIt first, InputIt last)
{
const qsizetype size = std::distance(first, last);
if (size == 0)
return QtFuture::makeReadyFuture(OutputSequence());
auto context = QSharedPointer<QtPrivate::WhenAllContext<OutputSequence>>::create(size);
context->futures.resize(size);
context->promise.reportStarted();
qsizetype idx = 0;
for (auto it = first; it != last; ++it, ++idx) {
it->then([context, idx](const ValueType &f) {
context->checkForCompletion(idx, f);
}).onCanceled([context, idx, f = *it] {
context->checkForCompletion(idx, f);
});
}
return context->promise.future();
}
template<typename OutputSequence, typename... Futures>
QFuture<OutputSequence> whenAllImpl(Futures &&... futures)
{
constexpr qsizetype size = sizeof...(Futures);
auto context = QSharedPointer<QtPrivate::WhenAllContext<OutputSequence>>::create(size);
context->futures.resize(size);
context->promise.reportStarted();
QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward<Futures>(futures)...));
return context->promise.future();
}
template<typename InputIt, typename ValueType>
QFuture<QtFuture::WhenAnyResult<typename Future<ValueType>::type>> whenAnyImpl(InputIt first,
InputIt last)
{
using PackagedType = typename Future<ValueType>::type;
using ResultType = QtFuture::WhenAnyResult<PackagedType>;
const qsizetype size = std::distance(first, last);
if (size == 0) {
return QtFuture::makeReadyFuture(
QtFuture::WhenAnyResult { qsizetype(-1), QFuture<PackagedType>() });
}
auto context = QSharedPointer<QtPrivate::WhenAnyContext<ResultType>>::create();
context->promise.reportStarted();
qsizetype idx = 0;
for (auto it = first; it != last; ++it, ++idx) {
it->then([context, idx](const ValueType &f) {
context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f });
}).onCanceled([context, idx, f = *it] {
context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f });
});
}
return context->promise.future();
}
template<typename... Futures>
QFuture<std::variant<std::decay_t<Futures>...>> whenAnyImpl(Futures &&... futures)
{
using ResultType = std::variant<std::decay_t<Futures>...>;
auto context = QSharedPointer<QtPrivate::WhenAnyContext<ResultType>>::create();
context->promise.reportStarted();
QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward<Futures>(futures)...));
return context->promise.future();
}
} // namespace QtPrivate
QT_END_NAMESPACE

View File

@ -168,6 +168,19 @@ private slots:
void getFutureInterface();
void convertQMetaType();
void whenAllIterators();
void whenAllIteratorsWithCanceled();
void whenAllIteratorsWithFailed();
void whenAllDifferentTypes();
void whenAllDifferentTypesWithCanceled();
void whenAllDifferentTypesWithFailed();
void whenAnyIterators();
void whenAnyIteratorsWithCanceled();
void whenAnyIteratorsWithFailed();
void whenAnyDifferentTypes();
void whenAnyDifferentTypesWithCanceled();
void whenAnyDifferentTypesWithFailed();
private:
using size_type = std::vector<int>::size_type;
@ -3730,5 +3743,506 @@ void tst_QFuture::convertQMetaType()
QVERIFY(voidFuture.isFinished());
}
template<class OutputContainer>
void testWhenAllIterators()
{
QPromise<int> p0;
QPromise<int> p1;
QPromise<int> p2;
QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() };
bool finished = false;
QFuture<OutputContainer> whenAll;
if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
whenAll = QtFuture::whenAll(futures.begin(), futures.end());
else
whenAll = QtFuture::whenAll<OutputContainer>(futures.begin(), futures.end());
whenAll.then([&](const OutputContainer &output) {
QCOMPARE(output.size(), 3u);
QCOMPARE(output[0].result(), 0);
QCOMPARE(output[1].result(), 1);
QCOMPARE(output[2].result(), 2);
finished = true;
});
QVERIFY(whenAll.isRunning());
p0.start();
p0.addResult(0);
p0.finish();
QVERIFY(whenAll.isRunning());
p2.start();
p2.addResult(2);
p2.finish();
QVERIFY(whenAll.isRunning());
p1.start();
p1.addResult(1);
p1.finish();
QVERIFY(!whenAll.isRunning());
QVERIFY(finished);
// Try with empty sequence
QFuture<OutputContainer> whenAllEmpty;
if constexpr (std::is_same_v<QList<QFuture<int>>, OutputContainer>)
whenAllEmpty = QtFuture::whenAll(futures.end(), futures.end());
else
whenAllEmpty = QtFuture::whenAll<OutputContainer>(futures.end(), futures.end());
QVERIFY(whenAllEmpty.isStarted());
QVERIFY(whenAllEmpty.isFinished());
QVERIFY(whenAllEmpty.result().empty());
}
void tst_QFuture::whenAllIterators()
{
// Try with different output containers
testWhenAllIterators<QList<QFuture<int>>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllIterators() with QList failed!");
testWhenAllIterators<std::vector<QFuture<int>>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllIterators() with std::vector failed!");
testWhenAllIterators<QVarLengthArray<QFuture<int>>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllIterators() with QVarLengthArray failed!");
}
void tst_QFuture::whenAllIteratorsWithCanceled()
{
QPromise<int> p0;
QPromise<int> p1;
QList<QFuture<int>> futures = { p0.future(), p1.future() };
bool finished = false;
auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
.then([&](const QList<QFuture<int>> &results) {
QCOMPARE(results.size(), 2);
QVERIFY(results[0].isCanceled());
QVERIFY(!results[1].isCanceled());
QCOMPARE(results[1].result(), 1);
finished = true;
});
p0.start();
p0.future().cancel();
p0.finish();
QVERIFY(!finished);
p1.start();
p1.addResult(1);
p1.finish();
QVERIFY(finished);
}
void tst_QFuture::whenAllIteratorsWithFailed()
{
#ifndef QT_NO_EXCEPTIONS
QPromise<int> p0;
QPromise<int> p1;
QList<QFuture<int>> futures = { p0.future(), p1.future() };
bool finished = false;
auto whenAll = QtFuture::whenAll(futures.begin(), futures.end())
.then([&](QList<QFuture<int>> results) {
QCOMPARE(results.size(), 2);
QCOMPARE(results[1].result(), 1);
// A shorter way of handling the exception
results[0].onFailed([&](const QException &) {
finished = true;
return 0;
});
});
p0.start();
p0.setException(QException());
p0.finish();
QVERIFY(!finished);
p1.start();
p1.addResult(1);
p1.finish();
QVERIFY(finished);
#else
QSKIP("Exceptions are disabled, skipping the test")
#endif
}
// A helper for std::visit, see https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts>
struct overloaded : public Ts...
{
using Ts::operator()...;
};
// explicit deduction guide
template<class... Ts>
overloaded(Ts...)->overloaded<Ts...>;
template<class OutputContainer>
void testWhenAllDifferentTypes()
{
QPromise<int> pInt1;
QPromise<int> pInt2;
QPromise<void> pVoid;
using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
QFuture<OutputContainer> whenAll;
if constexpr (std::is_same_v<QList<Futures>, OutputContainer>) {
whenAll = QtFuture::whenAll(pInt1.future(), pInt2.future(), pVoid.future());
} else {
whenAll =
QtFuture::whenAll<OutputContainer>(pInt1.future(), pInt2.future(), pVoid.future());
}
int sumOfInts = 0;
whenAll.then([&](const OutputContainer &results) {
for (auto future : results) {
std::visit(overloaded {
[&](const QFuture<int> &f) {
QVERIFY(f.isFinished());
sumOfInts += f.result();
},
[](const QFuture<void> &f) { QVERIFY(f.isFinished()); },
},
future);
}
});
pVoid.start();
pVoid.finish();
QVERIFY(whenAll.isRunning());
pInt2.start();
pInt2.addResult(2);
pInt2.finish();
QVERIFY(whenAll.isRunning());
QCOMPARE(sumOfInts, 0);
pInt1.start();
pInt1.addResult(1);
pInt1.finish();
QVERIFY(!whenAll.isRunning());
QCOMPARE(sumOfInts, 3);
}
void tst_QFuture::whenAllDifferentTypes()
{
using Futures = std::variant<QFuture<int>, QFuture<int>, QFuture<void>>;
testWhenAllDifferentTypes<QList<Futures>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllDifferentTypes() with QList failed!");
testWhenAllDifferentTypes<std::vector<Futures>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllDifferentTypes() with std::vector failed!");
testWhenAllDifferentTypes<QVarLengthArray<Futures>>();
if (QTest::currentTestFailed())
QSKIP("testWhenAllDifferentTypes() with QVarLengthArray failed!");
}
void tst_QFuture::whenAllDifferentTypesWithCanceled()
{
QPromise<int> pInt;
QPromise<QString> pString;
const QString someValue = u"some value"_qs;
bool finished = false;
using Futures = std::variant<QFuture<int>, QFuture<QString>>;
auto whenAll = QtFuture::whenAll(pInt.future(), pString.future())
.then([&](const QList<Futures> &results) {
finished = true;
for (auto future : results) {
std::visit(overloaded {
[](const QFuture<int> &f) {
QVERIFY(f.isFinished());
QVERIFY(f.isCanceled());
},
[&](const QFuture<QString> &f) {
QVERIFY(f.isFinished());
QCOMPARE(f.result(), someValue);
},
},
future);
}
});
pString.start();
pString.addResult(someValue);
pString.finish();
QVERIFY(!finished);
pInt.start();
pInt.future().cancel();
pInt.finish();
QVERIFY(finished);
}
void tst_QFuture::whenAllDifferentTypesWithFailed()
{
#ifndef QT_NO_EXCEPTIONS
QPromise<int> pInt;
QPromise<QString> pString;
const QString someValue = u"some value"_qs;
bool finished = false;
using Futures = std::variant<QFuture<int>, QFuture<QString>>;
auto whenAll = QtFuture::whenAll(pInt.future(), pString.future())
.then([&](const QList<Futures> &results) {
finished = true;
for (auto future : results) {
std::visit(overloaded {
[](QFuture<int> f) {
QVERIFY(f.isFinished());
bool failed = false;
// A shorter way of handling the exception
f.onFailed([&](const QException &) {
failed = true;
return -1;
});
QVERIFY(failed);
},
[&](const QFuture<QString> &f) {
QVERIFY(f.isFinished());
QCOMPARE(f.result(), someValue);
},
},
future);
}
});
pInt.start();
pInt.setException(QException());
pInt.finish();
QVERIFY(!finished);
pString.start();
pString.addResult(someValue);
pString.finish();
QVERIFY(finished);
#else
QSKIP("Exceptions are disabled, skipping the test")
#endif
}
void tst_QFuture::whenAnyIterators()
{
QPromise<int> p0;
QPromise<int> p1;
QPromise<int> p2;
QList<QFuture<int>> futures = { p0.future(), p1.future(), p2.future() };
auto whenAny = QtFuture::whenAny(futures.begin(), futures.end());
int count = 0;
whenAny.then([&](const QtFuture::WhenAnyResult<int> &result) {
QCOMPARE(result.index, 1);
QCOMPARE(result.future.result(), 1);
QVERIFY(!futures[0].isFinished());
QVERIFY(futures[1].isFinished());
QVERIFY(!futures[2].isFinished());
++count;
});
p0.start();
p1.start();
p2.start();
p0.addResult(0);
p1.addResult(1);
p2.addResult(2);
QVERIFY(!whenAny.isFinished());
QCOMPARE(count, 0);
p1.finish();
QVERIFY(whenAny.isFinished());
QCOMPARE(count, 1);
p0.finish();
QCOMPARE(count, 1);
p2.finish();
QCOMPARE(count, 1);
auto whenAnyEmpty = QtFuture::whenAny(futures.end(), futures.end());
QVERIFY(whenAnyEmpty.isStarted());
QVERIFY(whenAnyEmpty.isFinished());
QCOMPARE(whenAnyEmpty.result().index, -1);
auto whenAnyEmptyResult = whenAnyEmpty.result().future;
QVERIFY(whenAnyEmptyResult.isStarted());
QVERIFY(whenAnyEmptyResult.isFinished());
QVERIFY(whenAnyEmptyResult.isCanceled());
}
void tst_QFuture::whenAnyIteratorsWithCanceled()
{
QPromise<int> p0;
QPromise<int> p1;
QList<QFuture<int>> futures = { p0.future(), p1.future() };
int count = 0;
auto whenAny = QtFuture::whenAny(futures.begin(), futures.end())
.then([&](const QtFuture::WhenAnyResult<int> &result) {
QCOMPARE(result.index, 1);
QVERIFY(result.future.isCanceled());
QVERIFY(!futures[0].isFinished());
QVERIFY(futures[1].isFinished());
++count;
});
p1.start();
p1.future().cancel();
p1.finish();
QVERIFY(whenAny.isFinished());
QCOMPARE(count, 1);
p0.start();
p0.addResult(0);
p0.finish();
QCOMPARE(count, 1);
}
void tst_QFuture::whenAnyIteratorsWithFailed()
{
#ifndef QT_NO_EXCEPTIONS
QPromise<int> p0;
QPromise<int> p1;
QList<QFuture<int>> futures = { p0.future(), p1.future() };
int count = 0;
auto whenAny = QtFuture::whenAny(futures.begin(), futures.end())
.then([&](QtFuture::WhenAnyResult<int> result) {
QCOMPARE(result.index, 1);
QVERIFY(p1.future().isFinished());
QVERIFY(!p0.future().isFinished());
// A shorter way of handling the exception
result.future.onFailed([&](const QException &) {
++count;
return 0;
});
});
p1.start();
p1.setException(QException());
p1.finish();
QCOMPARE(count, 1);
p0.start();
p0.addResult(0);
p0.finish();
QCOMPARE(count, 1);
#else
QSKIP("Exceptions are disabled, skipping the test")
#endif
}
void tst_QFuture::whenAnyDifferentTypes()
{
QPromise<int> pInt1;
QPromise<int> pInt2;
QPromise<void> pVoid;
auto whenAny = QtFuture::whenAny(pInt1.future(), pInt2.future(), pVoid.future());
int count = 0;
whenAny.then([&](const std::variant<QFuture<int>, QFuture<int>, QFuture<void>> &result) {
QCOMPARE(result.index(), 1u);
std::visit(overloaded { [&](const QFuture<int> &future) {
QVERIFY(future.isFinished());
QCOMPARE(future.result(), 2);
++count;
},
[](auto) { QFAIL("The wrong future completed."); }
},
result);
});
pInt2.start();
pInt1.start();
pVoid.start();
pInt1.addResult(1);
pInt2.addResult(2);
QVERIFY(!whenAny.isFinished());
QCOMPARE(count, 0);
pInt2.finish();
QVERIFY(whenAny.isFinished());
QCOMPARE(count, 1);
pInt1.finish();
QCOMPARE(count, 1);
pVoid.finish();
QCOMPARE(count, 1);
}
void tst_QFuture::whenAnyDifferentTypesWithCanceled()
{
QPromise<int> pInt;
QPromise<void> pVoid;
int count = 0;
auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future())
.then([&](const std::variant<QFuture<int>, QFuture<void>> &result) {
QCOMPARE(result.index(), 0u);
std::visit(overloaded { [&](const QFuture<int> &future) {
QVERIFY(future.isFinished());
QVERIFY(future.isCanceled());
++count;
},
[](auto) {
QFAIL("The wrong future completed.");
}
},
result);
});
pInt.start();
pInt.future().cancel();
pInt.finish();
QCOMPARE(count, 1);
pVoid.start();
pVoid.finish();
QCOMPARE(count, 1);
}
void tst_QFuture::whenAnyDifferentTypesWithFailed()
{
#ifndef QT_NO_EXCEPTIONS
QPromise<int> pInt;
QPromise<void> pVoid;
int count = 0;
auto whenAny = QtFuture::whenAny(pInt.future(), pVoid.future())
.then([&](const std::variant<QFuture<int>, QFuture<void>> &result) {
QCOMPARE(result.index(), 0u);
std::visit(overloaded { [&](QFuture<int> future) {
QVERIFY(future.isFinished());
// A shorter way of handling the exception
future.onFailed([&](const QException &) {
++count;
return -1;
});
},
[](auto) {
QFAIL("The wrong future completed.");
}
},
result);
});
pInt.start();
pInt.setException(QException());
pInt.finish();
QCOMPARE(count, 1);
pVoid.start();
pVoid.finish();
QCOMPARE(count, 1);
#else
QSKIP("Exceptions are disabled, skipping the test")
#endif
}
QTEST_MAIN(tst_QFuture)
#include "tst_qfuture.moc"