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:
parent
3b49aa72fe
commit
102f7d31c4
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user