diff options
Diffstat (limited to 'src/corelib/thread')
-rw-r--r-- | src/corelib/thread/qfuture.h | 84 | ||||
-rw-r--r-- | src/corelib/thread/qfuture.qdoc | 148 | ||||
-rw-r--r-- | src/corelib/thread/qfuture_impl.h | 189 |
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 |