summaryrefslogtreecommitdiffstats
path: root/src/corelib/thread
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2021-11-04 17:01:54 +0100
committerSona Kurazyan <sona.kurazyan@qt.io>2021-11-20 10:28:29 +0100
commit102f7d31c469a546f52c930a047bd294fb198186 (patch)
tree45fe0355a7195e385b6b2457f79861bb7f3597bb /src/corelib/thread
parent3b49aa72fe6ec0dd0aa0c1c41fb81e874dc789fa (diff)
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>
Diffstat (limited to 'src/corelib/thread')
-rw-r--r--src/corelib/thread/qfuture.h84
-rw-r--r--src/corelib/thread/qfuture.qdoc148
-rw-r--r--src/corelib/thread/qfuture_impl.h189
3 files changed, 421 insertions, 0 deletions
diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h
index 094b3471b8..7aed38be16 100644
--- a/src/corelib/thread/qfuture.h
+++ b/src/corelib/thread/qfuture.h
@@ -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
diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc
index 16e20666df..1982594c81 100644
--- a/src/corelib/thread/qfuture.qdoc
+++ b/src/corelib/thread/qfuture.qdoc
@@ -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
+*/
diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h
index d7132caa7b..e5a15b57ff 100644
--- a/src/corelib/thread/qfuture_impl.h
+++ b/src/corelib/thread/qfuture_impl.h
@@ -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