From 102f7d31c469a546f52c930a047bd294fb198186 Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Thu, 4 Nov 2021 17:01:54 +0100 Subject: Add support for combining multiple QFutures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [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 Reviewed-by: MÃ¥rten Nordheim --- src/corelib/thread/qfuture.h | 84 +++++++++++++++++ src/corelib/thread/qfuture.qdoc | 148 +++++++++++++++++++++++++++++ src/corelib/thread/qfuture_impl.h | 189 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+) (limited to 'src/corelib/thread') 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 + friend struct QtPrivate::WhenAnyContext; + using QFuturePrivate = std::conditional_t, QFutureInterfaceBase, QFutureInterface>; @@ -456,6 +459,87 @@ struct MetaTypeQFutureHelper> } // namespace QtPrivate +namespace QtFuture { + +#ifndef Q_CLANG_QDOC + +template::value_type, + std::enable_if_t, + QtPrivate::IsRandomAccessible, + QtPrivate::isQFuture>, + int> = 0> +QFuture whenAll(InputIt first, InputIt last) +{ + return QtPrivate::whenAllImpl(first, last); +} + +template::value_type, + std::enable_if_t, + QtPrivate::isQFuture>, + int> = 0> +QFuture> whenAll(InputIt first, InputIt last) +{ + return QtPrivate::whenAllImpl, InputIt, ValueType>(first, last); +} + +template, + QtPrivate::NotEmpty, + QtPrivate::isQFuture>...>, + int> = 0> +QFuture whenAll(Futures &&... futures) +{ + return QtPrivate::whenAllImpl(std::forward(futures)...); +} + +template, + QtPrivate::isQFuture>...>, + int> = 0> +QFuture...>>> whenAll(Futures &&... futures) +{ + return QtPrivate::whenAllImpl...>>, Futures...>( + std::forward(futures)...); +} + +template::value_type, + std::enable_if_t, + QtPrivate::isQFuture>, + int> = 0> +QFuture::type>> whenAny(InputIt first, + InputIt last) +{ + return QtPrivate::whenAnyImpl(first, last); +} + +template, + QtPrivate::isQFuture>...>, + int> = 0> +QFuture...>> whenAny(Futures &&... futures) +{ + return QtPrivate::whenAnyImpl(std::forward(futures)...); +} + +#else + +template +QFuture whenAll(InputIt first, InputIt last); + +template +QFuture whenAll(Futures &&... futures); + +template +QFuture> whenAny(InputIt first, InputIt last); + +template +QFuture...>> 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} struct is used for packaging the copy and + the index of the first completed \c QFuture 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 static QFuture> 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 QFuture 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. 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 QFuture 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. For each \c QFuture passed to + \c whenAll(), the entry at the corresponding position in \c OutputSequence + will be a \c std::variant holding that \c QFuture, 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. 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 QFuture> 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} 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 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 QFuture...>> 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 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 QPromise; namespace QtFuture { + enum class Launch { Sync, Async, Inherit }; + +template +struct WhenAnyResult +{ + qsizetype index = -1; + QFuture future; +}; + +// Deduction guide +template +WhenAnyResult(qsizetype, const QFuture &) -> WhenAnyResult; } namespace QtPrivate { @@ -244,6 +256,40 @@ struct isTuple> : std::true_type template inline constexpr bool isTupleV = isTuple::value; +template +inline constexpr bool isQFutureV = false; + +template +inline constexpr bool isQFutureV> = true; + +template +using isQFuture = std::bool_constant>; + +template +struct Future +{ +}; + +template +struct Future> +{ + using type = T; +}; + +template +using NotEmpty = std::bool_constant<(sizeof...(Args) > 0)>; + +template +using IsRandomAccessible = + std::is_convertible()))>>::iterator_category, + std::random_access_iterator_tag>; + +template +using IsForwardIterable = + std::is_convertible::iterator_category, + std::forward_iterator_tag>; + template class Continuation { @@ -906,4 +952,147 @@ static QFuture makeExceptionalFuture(const QException &exception) } // namespace QtFuture +namespace QtPrivate { + +template +struct WhenAllContext +{ + using ValueType = typename ResultFutures::value_type; + + WhenAllContext(qsizetype size) : count(size) {} + + template + void checkForCompletion(qsizetype index, T &&future) + { + futures[index] = std::forward(future); + Q_ASSERT(count > 0); + if (--count <= 0) { + promise.reportResult(futures); + promise.reportFinished(); + } + } + + QAtomicInteger count; + QFutureInterface promise; + ResultFutures futures; +}; + +template +struct WhenAnyContext +{ + using ValueType = ResultType; + + template> + void checkForCompletion(qsizetype, T &&result) + { + if (!ready.fetchAndStoreRelaxed(true)) { + promise.reportResult(std::forward(result)); + promise.reportFinished(); + } + } + + QAtomicInt ready = false; + QFutureInterface promise; +}; + +template +void addCompletionHandlersImpl(const QSharedPointer &context, + const std::tuple &t) +{ + auto future = std::get(t); + using ResultType = typename ContextType::ValueType; + future.then([context](const std::tuple_element_t> &f) { + context->checkForCompletion(Index, ResultType { std::in_place_index, f }); + }).onCanceled([context, future]() { + context->checkForCompletion(Index, ResultType { std::in_place_index, future }); + }); + + if constexpr (Index != 0) + addCompletionHandlersImpl(context, t); +} + +template +void addCompletionHandlers(const QSharedPointer &context, const std::tuple &t) +{ + constexpr qsizetype size = std::tuple_size>::value; + addCompletionHandlersImpl(context, t); +} + +template +QFuture whenAllImpl(InputIt first, InputIt last) +{ + const qsizetype size = std::distance(first, last); + if (size == 0) + return QtFuture::makeReadyFuture(OutputSequence()); + + auto context = QSharedPointer>::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 +QFuture whenAllImpl(Futures &&... futures) +{ + constexpr qsizetype size = sizeof...(Futures); + auto context = QSharedPointer>::create(size); + context->futures.resize(size); + context->promise.reportStarted(); + + QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward(futures)...)); + + return context->promise.future(); +} + +template +QFuture::type>> whenAnyImpl(InputIt first, + InputIt last) +{ + using PackagedType = typename Future::type; + using ResultType = QtFuture::WhenAnyResult; + + const qsizetype size = std::distance(first, last); + if (size == 0) { + return QtFuture::makeReadyFuture( + QtFuture::WhenAnyResult { qsizetype(-1), QFuture() }); + } + + auto context = QSharedPointer>::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 +QFuture...>> whenAnyImpl(Futures &&... futures) +{ + using ResultType = std::variant...>; + + auto context = QSharedPointer>::create(); + context->promise.reportStarted(); + + QtPrivate::addCompletionHandlers(context, std::make_tuple(std::forward(futures)...)); + + return context->promise.future(); +} + +} // namespace QtPrivate + QT_END_NAMESPACE -- cgit v1.2.3