QFuture - add ability to move results from QFuture
QFuture's original design pre-dates C++11 and its introduction of move semantics. QFuture is documented as requiring copy-constructible classes and uses copy operations for results (which in Qt's universe in general is relatively cheap, due to the use of COW/data sharing). QFuture::result(), QFuture::results(), QFuture::resultAt() return copies. Now that the year is 2020, it makes some sense to add support for move semantics and, in particular, move-only types, like std::unique_ptr (that cannot be obtained from QFuture using result etc.). Taking a result or results from a QFuture renders it invalid. This patch adds QFuture<T>::takeResults(), takeResult() and isValid(). 'Taking' functions are 'enabled_if' for non-void types only to improve the compiler's diagnostic (which would otherwise spit some semi-articulate diagnostic). As a bonus a bug was found in the pre-existing code (after initially copy and pasted into the new function) - the one where we incorrectly report ready results in (rather obscure) filter mode. Fixes: QTBUG-81941 Fixes: QTBUG-83182 Change-Id: I8ccdfc50aa310a3a79eef2cdc55f5ea210f889c3 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
986cfe312e
commit
44ceb56455
@ -48,6 +48,9 @@
|
|||||||
#include <QtCore/qrunnable.h>
|
#include <QtCore/qrunnable.h>
|
||||||
#include <QtCore/qthreadpool.h>
|
#include <QtCore/qthreadpool.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +126,11 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if constexpr (std::is_move_constructible_v<T>)
|
||||||
|
this->reportAndMoveResult(std::move(result));
|
||||||
|
else if constexpr (std::is_copy_constructible_v<T>)
|
||||||
this->reportResult(result);
|
this->reportResult(result);
|
||||||
|
|
||||||
this->reportFinished();
|
this->reportFinished();
|
||||||
}
|
}
|
||||||
T result;
|
T result;
|
||||||
|
@ -47,6 +47,9 @@
|
|||||||
|
|
||||||
#include <QtCore/qfuture_impl.h>
|
#include <QtCore/qfuture_impl.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
QT_REQUIRE_CONFIG(future);
|
QT_REQUIRE_CONFIG(future);
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -57,6 +60,10 @@ class QFutureWatcher;
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
class QFuture
|
class QFuture
|
||||||
{
|
{
|
||||||
|
static_assert (std::is_copy_constructible_v<T>
|
||||||
|
|| std::is_move_constructible_v<T>
|
||||||
|
|| std::is_same_v<T, void>,
|
||||||
|
"Type with copy or move constructors or type void is required");
|
||||||
public:
|
public:
|
||||||
QFuture()
|
QFuture()
|
||||||
: d(QFutureInterface<T>::canceledResult())
|
: d(QFutureInterface<T>::canceledResult())
|
||||||
@ -135,6 +142,14 @@ public:
|
|||||||
template<typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
|
template<typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
|
||||||
QList<T> results() const { return d.results(); }
|
QList<T> results() const { return d.results(); }
|
||||||
|
|
||||||
|
template<typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
|
||||||
|
T takeResult() { return d.takeResult(); }
|
||||||
|
|
||||||
|
template<typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
|
||||||
|
std::vector<T> takeResults() { return d.takeResults(); }
|
||||||
|
|
||||||
|
bool isValid() const { return d.isValid(); }
|
||||||
|
|
||||||
template<class Function>
|
template<class Function>
|
||||||
using ResultType = typename QtPrivate::ResultTypeHelper<Function, T>::ResultType;
|
using ResultType = typename QtPrivate::ResultTypeHelper<Function, T>::ResultType;
|
||||||
|
|
||||||
|
@ -37,14 +37,17 @@
|
|||||||
|
|
||||||
QFuture allows threads to be synchronized against one or more results
|
QFuture allows threads to be synchronized against one or more results
|
||||||
which will be ready at a later point in time. The result can be of any type
|
which will be ready at a later point in time. The result can be of any type
|
||||||
that has a default constructor and a copy constructor. If a result is not
|
that has default, copy and possibly move constructors. If
|
||||||
available at the time of calling the result(), resultAt(), or results()
|
a result is not available at the time of calling the result(), resultAt(),
|
||||||
functions, QFuture will wait until the result becomes available. You can
|
results(), takeResult(), or takeResults() functions, QFuture
|
||||||
use the isResultReadyAt() function to determine if a result is ready or
|
will wait until the result becomes available. You can use the isResultReadyAt()
|
||||||
not. For QFuture objects that report more than one result, the
|
function to determine if a result is ready or not. For QFuture objects that
|
||||||
resultCount() function returns the number of continuous results. This
|
report more than one result, the resultCount() function returns the number
|
||||||
means that it is always safe to iterate through the results from 0 to
|
of continuous results. This means that it is always safe to iterate through
|
||||||
resultCount().
|
the results from 0 to resultCount(). takeResult() and takeResults()
|
||||||
|
invalidate a future and any subsequent attempt to access result or results
|
||||||
|
from the future leads to undefined behavior. isValid() tells you if
|
||||||
|
results can be accessed.
|
||||||
|
|
||||||
QFuture provides a \l{Java-style iterators}{Java-style iterator}
|
QFuture provides a \l{Java-style iterators}{Java-style iterator}
|
||||||
(QFutureIterator) and an \l{STL-style iterators}{STL-style iterator}
|
(QFutureIterator) and an \l{STL-style iterators}{STL-style iterator}
|
||||||
@ -227,7 +230,7 @@
|
|||||||
number of results stored might be different from this value, due to gaps
|
number of results stored might be different from this value, due to gaps
|
||||||
in the result set. It is always safe to iterate through the results from 0
|
in the result set. It is always safe to iterate through the results from 0
|
||||||
to resultCount().
|
to resultCount().
|
||||||
\sa result(), resultAt(), results()
|
\sa result(), resultAt(), results(), takeResult(), takeResults()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> int QFuture<T>::progressValue() const
|
/*! \fn template <typename T> int QFuture<T>::progressValue() const
|
||||||
@ -273,7 +276,10 @@
|
|||||||
available, this function will block and wait for the result to become
|
available, this function will block and wait for the result to become
|
||||||
available. This is a convenience method for calling resultAt(0).
|
available. This is a convenience method for calling resultAt(0).
|
||||||
|
|
||||||
\sa resultAt(), results()
|
\note Calling result() leads to undefined behavior if isValid()
|
||||||
|
returns \c false for this QFuture.
|
||||||
|
|
||||||
|
\sa resultAt(), results(), takeResult(), takeResults()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> T QFuture<T>::resultAt(int index) const
|
/*! \fn template <typename T> T QFuture<T>::resultAt(int index) const
|
||||||
@ -282,7 +288,10 @@
|
|||||||
immediately available, this function will block and wait for the result to
|
immediately available, this function will block and wait for the result to
|
||||||
become available.
|
become available.
|
||||||
|
|
||||||
\sa result(), results(), resultCount()
|
\note Calling resultAt() leads to undefined behavior if isValid()
|
||||||
|
returns \c false for this QFuture.
|
||||||
|
|
||||||
|
\sa result(), results(), takeResult(), takeResults(), resultCount()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> bool QFuture<T>::isResultReadyAt(int index) const
|
/*! \fn template <typename T> bool QFuture<T>::isResultReadyAt(int index) const
|
||||||
@ -290,7 +299,10 @@
|
|||||||
Returns \c true if the result at \a index is immediately available; otherwise
|
Returns \c true if the result at \a index is immediately available; otherwise
|
||||||
returns \c false.
|
returns \c false.
|
||||||
|
|
||||||
\sa resultAt(), resultCount()
|
\note Calling isResultReadyAt() leads to undefined behavior if isValid()
|
||||||
|
returns \c false for this QFuture.
|
||||||
|
|
||||||
|
\sa resultAt(), resultCount(), takeResult(), takeResults()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> QFuture<T>::operator T() const
|
/*! \fn template <typename T> QFuture<T>::operator T() const
|
||||||
@ -300,15 +312,69 @@
|
|||||||
available. This is a convenience method for calling result() or
|
available. This is a convenience method for calling result() or
|
||||||
resultAt(0).
|
resultAt(0).
|
||||||
|
|
||||||
\sa result(), resultAt(), results()
|
\note Calling this function leads to undefined behavior if isValid()
|
||||||
|
returns \c false for this QFuture.
|
||||||
|
|
||||||
|
\sa result(), resultAt(), results(), takeResult(), takeResults(), isValid()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> QList<T> QFuture<T>::results() const
|
/*! \fn template <typename T> QList<T> QFuture<T>::results() const
|
||||||
|
|
||||||
Returns all results from the future. If the results are not immediately
|
Returns all results from the future. If the results are not immediately available,
|
||||||
available, this function will block and wait for them to become available.
|
this function will block and wait for them to become available.
|
||||||
|
|
||||||
\sa result(), resultAt(), resultCount()
|
\note Calling results() leads to undefined behavior if isValid()
|
||||||
|
returns \c false for this QFuture.
|
||||||
|
|
||||||
|
\sa result(), resultAt(), takeResult(), takeResults(), resultCount(), isValid()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \fn template <typename T> std::vector<T> QFuture<T>::takeResults()
|
||||||
|
|
||||||
|
If isValid() returns \c false, calling this function leads to undefined behavior.
|
||||||
|
takeResults() takes all results from the QFuture object and invalidates it
|
||||||
|
(isValid() will return \c false for this future). If the results are
|
||||||
|
not immediately available, this function will block and wait for them to
|
||||||
|
become available. This function tries to use move semantics for the results
|
||||||
|
if available and falls back to copy construction if the type is not movable.
|
||||||
|
|
||||||
|
\note QFuture in general allows sharing the results between different QFuture
|
||||||
|
objects (and potentially between different threads). takeResults() was introduced
|
||||||
|
to make QFuture also work with move-only types (like std::unique_ptr), so it
|
||||||
|
assumes that only one thread can move the results out of the future, and only
|
||||||
|
once.
|
||||||
|
|
||||||
|
\sa takeResult(), result(), resultAt(), results(), resultCount(), isValid()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* \fn template <typename T> std::vector<T> QFuture<T>::takeResult()
|
||||||
|
|
||||||
|
Call this function only if isValid() returns \c true, otherwise
|
||||||
|
the behavior is undefined. This function takes the first result from
|
||||||
|
the QFuture object, for convenience when only one result is expected.
|
||||||
|
If there are any other results, they are discarded after taking the
|
||||||
|
first one (if such behavior is undesired, use takeResults() instead).
|
||||||
|
If the result is not immediately available, this function will block and
|
||||||
|
wait for the result to become available. The QFuture will try to use move
|
||||||
|
semantics if possible, and will fall back to copy construction if the type
|
||||||
|
is not movable. After the result was taken, isValid() will evaluate
|
||||||
|
as \c false.
|
||||||
|
|
||||||
|
\note QFuture in general allows sharing the results between different QFuture
|
||||||
|
objects (and potentially between different threads). takeResult() was introduced
|
||||||
|
to make QFuture also work with move-only types (like std::unique_ptr), so it
|
||||||
|
assumes that only one thread can move the results out of the future, and
|
||||||
|
do it only once.
|
||||||
|
|
||||||
|
\sa takeResults(), result(), results(), resultAt(), isValid()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* \fn template <typename T> std::vector<T> QFuture<T>::isValid() const
|
||||||
|
|
||||||
|
Returns true if a result or results can be accessed or taken from this
|
||||||
|
QFuture object. Returns false after the result was taken from the future.
|
||||||
|
|
||||||
|
\sa takeResults(), takeResult(), result(), results(), resultAt()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn template <typename T> QFuture<T>::const_iterator QFuture<T>::begin() const
|
/*! \fn template <typename T> QFuture<T>::const_iterator QFuture<T>::begin() const
|
||||||
|
@ -114,6 +114,7 @@ void QFutureInterfaceBase::cancel()
|
|||||||
d->waitCondition.wakeAll();
|
d->waitCondition.wakeAll();
|
||||||
d->pausedWaitCondition.wakeAll();
|
d->pausedWaitCondition.wakeAll();
|
||||||
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
|
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Canceled));
|
||||||
|
d->isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QFutureInterfaceBase::setPaused(bool paused)
|
void QFutureInterfaceBase::setPaused(bool paused)
|
||||||
@ -191,6 +192,12 @@ bool QFutureInterfaceBase::isResultReadyAt(int index) const
|
|||||||
return d->internal_isResultReadyAt(index);
|
return d->internal_isResultReadyAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QFutureInterfaceBase::isValid() const
|
||||||
|
{
|
||||||
|
const QMutexLocker lock(&d->m_mutex);
|
||||||
|
return d->isValid;
|
||||||
|
}
|
||||||
|
|
||||||
bool QFutureInterfaceBase::isRunningOrPending() const
|
bool QFutureInterfaceBase::isRunningOrPending() const
|
||||||
{
|
{
|
||||||
return queryState(static_cast<State>(Running | Pending));
|
return queryState(static_cast<State>(Running | Pending));
|
||||||
@ -263,9 +270,9 @@ void QFutureInterfaceBase::reportStarted()
|
|||||||
QMutexLocker locker(&d->m_mutex);
|
QMutexLocker locker(&d->m_mutex);
|
||||||
if (d->state.loadRelaxed() & (Started|Canceled|Finished))
|
if (d->state.loadRelaxed() & (Started|Canceled|Finished))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
d->setState(State(Started | Running));
|
d->setState(State(Started | Running));
|
||||||
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Started));
|
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Started));
|
||||||
|
d->isValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QFutureInterfaceBase::reportCanceled()
|
void QFutureInterfaceBase::reportCanceled()
|
||||||
@ -473,6 +480,16 @@ bool QFutureInterfaceBase::derefT() const
|
|||||||
return d->refCount.derefT();
|
return d->refCount.derefT();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QFutureInterfaceBase::reset()
|
||||||
|
{
|
||||||
|
d->m_progressValue = 0;
|
||||||
|
d->m_progressMinimum = 0;
|
||||||
|
d->m_progressMaximum = 0;
|
||||||
|
d->setState(QFutureInterfaceBase::NoState);
|
||||||
|
d->progressTime.invalidate();
|
||||||
|
d->isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
QFutureInterfaceBasePrivate::QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState)
|
QFutureInterfaceBasePrivate::QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState)
|
||||||
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
|
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
|
||||||
state(initialState),
|
state(initialState),
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
#include <QtCore/qexception.h>
|
#include <QtCore/qexception.h>
|
||||||
#include <QtCore/qresultstore.h>
|
#include <QtCore/qresultstore.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
QT_REQUIRE_CONFIG(future);
|
QT_REQUIRE_CONFIG(future);
|
||||||
@ -116,6 +118,7 @@ public:
|
|||||||
bool isPaused() const;
|
bool isPaused() const;
|
||||||
bool isThrottled() const;
|
bool isThrottled() const;
|
||||||
bool isResultReadyAt(int index) const;
|
bool isResultReadyAt(int index) const;
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
void cancel();
|
void cancel();
|
||||||
void setPaused(bool paused);
|
void setPaused(bool paused);
|
||||||
@ -139,6 +142,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
bool refT() const;
|
bool refT() const;
|
||||||
bool derefT() const;
|
bool derefT() const;
|
||||||
|
void reset();
|
||||||
public:
|
public:
|
||||||
|
|
||||||
#ifndef QFUTURE_TEST
|
#ifndef QFUTURE_TEST
|
||||||
@ -198,13 +202,22 @@ public:
|
|||||||
inline QFuture<T> future(); // implemented in qfuture.h
|
inline QFuture<T> future(); // implemented in qfuture.h
|
||||||
|
|
||||||
inline void reportResult(const T *result, int index = -1);
|
inline void reportResult(const T *result, int index = -1);
|
||||||
|
inline void reportAndMoveResult(T &&result, int index = -1);
|
||||||
inline void reportResult(const T &result, int index = -1);
|
inline void reportResult(const T &result, int index = -1);
|
||||||
inline void reportResults(const QVector<T> &results, int beginIndex = -1, int count = -1);
|
inline void reportResults(const QVector<T> &results, int beginIndex = -1, int count = -1);
|
||||||
inline void reportFinished(const T *result = nullptr);
|
inline void reportFinished(const T *result);
|
||||||
|
void reportFinished()
|
||||||
|
{
|
||||||
|
QFutureInterfaceBase::reportFinished();
|
||||||
|
QFutureInterfaceBase::runContinuation();
|
||||||
|
}
|
||||||
|
|
||||||
inline const T &resultReference(int index) const;
|
inline const T &resultReference(int index) const;
|
||||||
inline const T *resultPointer(int index) const;
|
inline const T *resultPointer(int index) const;
|
||||||
inline QList<T> results();
|
inline QList<T> results();
|
||||||
|
|
||||||
|
T takeResult();
|
||||||
|
std::vector<T> takeResults();
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -220,13 +233,28 @@ inline void QFutureInterface<T>::reportResult(const T *result, int index)
|
|||||||
if (store.filterMode()) {
|
if (store.filterMode()) {
|
||||||
const int resultCountBefore = store.count();
|
const int resultCountBefore = store.count();
|
||||||
store.addResult<T>(index, result);
|
store.addResult<T>(index, result);
|
||||||
this->reportResultsReady(resultCountBefore, resultCountBefore + store.count());
|
this->reportResultsReady(resultCountBefore, store.count());
|
||||||
} else {
|
} else {
|
||||||
const int insertIndex = store.addResult<T>(index, result);
|
const int insertIndex = store.addResult<T>(index, result);
|
||||||
this->reportResultsReady(insertIndex, insertIndex + 1);
|
this->reportResultsReady(insertIndex, insertIndex + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void QFutureInterface<T>::reportAndMoveResult(T &&result, int index)
|
||||||
|
{
|
||||||
|
std::lock_guard<QMutex> locker{mutex()};
|
||||||
|
if (queryState(Canceled) || queryState(Finished))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QtPrivate::ResultStoreBase &store = resultStoreBase();
|
||||||
|
|
||||||
|
const int oldResultCount = store.count();
|
||||||
|
const int insertIndex = store.moveResult(index, std::forward<T>(result));
|
||||||
|
if (!store.filterMode() || oldResultCount < store.count()) // Let's make sure it's not in pending results.
|
||||||
|
reportResultsReady(insertIndex, store.count());
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline void QFutureInterface<T>::reportResult(const T &result, int index)
|
inline void QFutureInterface<T>::reportResult(const T &result, int index)
|
||||||
{
|
{
|
||||||
@ -258,8 +286,7 @@ inline void QFutureInterface<T>::reportFinished(const T *result)
|
|||||||
{
|
{
|
||||||
if (result)
|
if (result)
|
||||||
reportResult(result);
|
reportResult(result);
|
||||||
QFutureInterfaceBase::reportFinished();
|
reportFinished();
|
||||||
QFutureInterfaceBase::runContinuation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -283,6 +310,7 @@ inline QList<T> QFutureInterface<T>::results()
|
|||||||
exceptionStore().throwPossibleException();
|
exceptionStore().throwPossibleException();
|
||||||
return QList<T>();
|
return QList<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QFutureInterfaceBase::waitForResult(-1);
|
QFutureInterfaceBase::waitForResult(-1);
|
||||||
|
|
||||||
QList<T> res;
|
QList<T> res;
|
||||||
@ -297,6 +325,56 @@ inline QList<T> QFutureInterface<T>::results()
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T QFutureInterface<T>::takeResult()
|
||||||
|
{
|
||||||
|
if (isCanceled()) {
|
||||||
|
exceptionStore().throwPossibleException();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid())
|
||||||
|
return {};
|
||||||
|
// Note: we wait for all, this is intentional,
|
||||||
|
// not to mess with other unready results.
|
||||||
|
waitForResult(-1);
|
||||||
|
|
||||||
|
const std::lock_guard<QMutex> locker{mutex()};
|
||||||
|
QtPrivate::ResultIteratorBase position = resultStoreBase().resultAt(0);
|
||||||
|
T ret(std::move_if_noexcept(position.value<T>()));
|
||||||
|
reset();
|
||||||
|
resultStoreBase().template clear<T>();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::vector<T> QFutureInterface<T>::takeResults()
|
||||||
|
{
|
||||||
|
if (isCanceled()) {
|
||||||
|
exceptionStore().throwPossibleException();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
waitForResult(-1);
|
||||||
|
std::vector<T> res;
|
||||||
|
res.reserve(resultCount());
|
||||||
|
|
||||||
|
const std::lock_guard<QMutex> locker{mutex()};
|
||||||
|
|
||||||
|
QtPrivate::ResultIteratorBase it = resultStoreBase().begin();
|
||||||
|
for (auto endIt = resultStoreBase().end(); it != endIt; ++it)
|
||||||
|
res.push_back(std::move_if_noexcept(it.value<T>()));
|
||||||
|
|
||||||
|
reset();
|
||||||
|
resultStoreBase().template clear<T>();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
class QFutureInterface<void> : public QFutureInterfaceBase
|
class QFutureInterface<void> : public QFutureInterfaceBase
|
||||||
{
|
{
|
||||||
|
@ -197,6 +197,7 @@ public:
|
|||||||
QBasicMutex continuationMutex;
|
QBasicMutex continuationMutex;
|
||||||
|
|
||||||
bool launchAsync = false;
|
bool launchAsync = false;
|
||||||
|
bool isValid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
#include <QtCore/qmap.h>
|
#include <QtCore/qmap.h>
|
||||||
#include <QtCore/qdebug.h>
|
#include <QtCore/qdebug.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
QT_REQUIRE_CONFIG(future);
|
QT_REQUIRE_CONFIG(future);
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -97,6 +99,19 @@ public:
|
|||||||
return *pointer<T>();
|
return *pointer<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T &value()
|
||||||
|
{
|
||||||
|
return *pointer<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T *pointer()
|
||||||
|
{
|
||||||
|
const T *p = qAsConst(*this).pointer<T>();
|
||||||
|
return const_cast<T *>(p);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
const T *pointer() const
|
const T *pointer() const
|
||||||
{
|
{
|
||||||
@ -144,10 +159,16 @@ public:
|
|||||||
{
|
{
|
||||||
if (result == nullptr)
|
if (result == nullptr)
|
||||||
return addResult(index, static_cast<void *>(nullptr));
|
return addResult(index, static_cast<void *>(nullptr));
|
||||||
else
|
|
||||||
return addResult(index, static_cast<void *>(new T(*result)));
|
return addResult(index, static_cast<void *>(new T(*result)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
int moveResult(int index, T &&result)
|
||||||
|
{
|
||||||
|
return addResult(index, static_cast<void *>(new T(std::move_if_noexcept(result))));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
int addResults(int index, const QVector<T> *results)
|
int addResults(int index, const QVector<T> *results)
|
||||||
{
|
{
|
||||||
@ -159,7 +180,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (m_filterMode == true && results->count() != totalCount && 0 == results->count())
|
if (m_filterMode == true && results->count() != totalCount && 0 == results->count())
|
||||||
return addResults(index, nullptr, 0, totalCount);
|
return addResults(index, nullptr, 0, totalCount);
|
||||||
else
|
|
||||||
return addResults(index, new QVector<T>(*results), results->count(), totalCount);
|
return addResults(index, new QVector<T>(*results), results->count(), totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,12 @@
|
|||||||
#include <qthreadpool.h>
|
#include <qthreadpool.h>
|
||||||
#include <qexception.h>
|
#include <qexception.h>
|
||||||
#include <qrandom.h>
|
#include <qrandom.h>
|
||||||
|
#include <QtConcurrent/qtconcurrentrun.h>
|
||||||
#include <private/qfutureinterface_p.h>
|
#include <private/qfutureinterface_p.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// COM interface macro.
|
// COM interface macro.
|
||||||
#if defined(Q_OS_WIN) && defined(interface)
|
#if defined(Q_OS_WIN) && defined(interface)
|
||||||
# undef interface
|
# undef interface
|
||||||
@ -101,6 +105,23 @@ private slots:
|
|||||||
void thenOnExceptionFuture();
|
void thenOnExceptionFuture();
|
||||||
void thenThrows();
|
void thenThrows();
|
||||||
#endif
|
#endif
|
||||||
|
void takeResults();
|
||||||
|
void takeResult();
|
||||||
|
void runAndTake();
|
||||||
|
void resultsReadyAt_data();
|
||||||
|
void resultsReadyAt();
|
||||||
|
private:
|
||||||
|
using size_type = std::vector<int>::size_type;
|
||||||
|
using UniquePtr = std::unique_ptr<int>;
|
||||||
|
|
||||||
|
static void testSingleResult(const UniquePtr &p);
|
||||||
|
static void testSingleResult(const std::vector<int> &v);
|
||||||
|
template<class T>
|
||||||
|
static void testSingleResult(const T &unknown);
|
||||||
|
template<class T>
|
||||||
|
static void testFutureTaken(QFuture<T> &noMoreFuture);
|
||||||
|
template<class T>
|
||||||
|
static void testTakeResults(QFuture<T> future, size_type resultCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_QFuture::resultStore()
|
void tst_QFuture::resultStore()
|
||||||
@ -1173,7 +1194,6 @@ void tst_QFuture::iterators()
|
|||||||
void tst_QFuture::iteratorsThread()
|
void tst_QFuture::iteratorsThread()
|
||||||
{
|
{
|
||||||
const int expectedResultCount = 10;
|
const int expectedResultCount = 10;
|
||||||
const int delay = 10;
|
|
||||||
QFutureInterface<int> futureInterface;
|
QFutureInterface<int> futureInterface;
|
||||||
|
|
||||||
// Create result producer thread. The results are
|
// Create result producer thread. The results are
|
||||||
@ -2061,5 +2081,208 @@ void tst_QFuture::thenThrows()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void tst_QFuture::testSingleResult(const UniquePtr &p)
|
||||||
|
{
|
||||||
|
QVERIFY(p.get() != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::testSingleResult(const std::vector<int> &v)
|
||||||
|
{
|
||||||
|
QVERIFY(!v.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void tst_QFuture::testSingleResult(const T &unknown)
|
||||||
|
{
|
||||||
|
Q_UNUSED(unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void tst_QFuture::testFutureTaken(QFuture<T> &noMoreFuture)
|
||||||
|
{
|
||||||
|
QCOMPARE(noMoreFuture.isValid(), false);
|
||||||
|
QCOMPARE(noMoreFuture.resultCount(), 0);
|
||||||
|
QCOMPARE(noMoreFuture.isStarted(), false);
|
||||||
|
QCOMPARE(noMoreFuture.isRunning(), false);
|
||||||
|
QCOMPARE(noMoreFuture.isPaused(), false);
|
||||||
|
QCOMPARE(noMoreFuture.isFinished(), false);
|
||||||
|
QCOMPARE(noMoreFuture.progressValue(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void tst_QFuture::testTakeResults(QFuture<T> future, size_type resultCount)
|
||||||
|
{
|
||||||
|
auto copy = future;
|
||||||
|
QVERIFY(future.isFinished());
|
||||||
|
QVERIFY(future.isValid());
|
||||||
|
QCOMPARE(size_type(future.resultCount()), resultCount);
|
||||||
|
QVERIFY(copy.isFinished());
|
||||||
|
QVERIFY(copy.isValid());
|
||||||
|
QCOMPARE(size_type(copy.resultCount()), resultCount);
|
||||||
|
|
||||||
|
auto vec = future.takeResults();
|
||||||
|
QCOMPARE(vec.size(), resultCount);
|
||||||
|
|
||||||
|
for (const auto &r : vec) {
|
||||||
|
testSingleResult(r);
|
||||||
|
if (QTest::currentTestFailed())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
testFutureTaken(future);
|
||||||
|
if (QTest::currentTestFailed())
|
||||||
|
return;
|
||||||
|
testFutureTaken(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::takeResults()
|
||||||
|
{
|
||||||
|
// Test takeResults() for movable types (whether or not copyable).
|
||||||
|
|
||||||
|
// std::unique_ptr<int> supports only move semantics:
|
||||||
|
QFutureInterface<UniquePtr> moveIface;
|
||||||
|
moveIface.reportStarted();
|
||||||
|
|
||||||
|
// std::vector<int> supports both copy and move:
|
||||||
|
QFutureInterface<std::vector<int>> copyIface;
|
||||||
|
copyIface.reportStarted();
|
||||||
|
|
||||||
|
const int expectedCount = 10;
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedCount; ++i) {
|
||||||
|
moveIface.reportAndMoveResult(UniquePtr{new int(0b101010)}, i);
|
||||||
|
copyIface.reportAndMoveResult(std::vector<int>{1,2,3,4,5}, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveIface.reportFinished();
|
||||||
|
copyIface.reportFinished();
|
||||||
|
|
||||||
|
testTakeResults(moveIface.future(), size_type(expectedCount));
|
||||||
|
if (QTest::currentTestFailed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
testTakeResults(copyIface.future(), size_type(expectedCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::takeResult()
|
||||||
|
{
|
||||||
|
QFutureInterface<UniquePtr> iface;
|
||||||
|
iface.reportStarted();
|
||||||
|
iface.reportAndMoveResult(UniquePtr{new int(0b101010)}, 0);
|
||||||
|
iface.reportFinished();
|
||||||
|
|
||||||
|
auto future = iface.future();
|
||||||
|
QVERIFY(future.isFinished());
|
||||||
|
QVERIFY(future.isValid());
|
||||||
|
QCOMPARE(future.resultCount(), 1);
|
||||||
|
|
||||||
|
auto result = future.takeResult();
|
||||||
|
testFutureTaken(future);
|
||||||
|
if (QTest::currentTestFailed())
|
||||||
|
return;
|
||||||
|
testSingleResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::runAndTake()
|
||||||
|
{
|
||||||
|
// Test if a 'moving' future can be used by
|
||||||
|
// QtConcurrent::run.
|
||||||
|
|
||||||
|
auto rabbit = [](){
|
||||||
|
// Let's wait a bit to give the test below some time
|
||||||
|
// to sync up with us with its watcher.
|
||||||
|
QThread::currentThread()->msleep(100);
|
||||||
|
return UniquePtr(new int(10));
|
||||||
|
};
|
||||||
|
|
||||||
|
QTestEventLoop loop;
|
||||||
|
QFutureWatcher<UniquePtr> watcha;
|
||||||
|
connect(&watcha, &QFutureWatcher<UniquePtr>::finished, [&loop](){
|
||||||
|
loop.exitLoop();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto gotcha = QtConcurrent::run(rabbit);
|
||||||
|
watcha.setFuture(gotcha);
|
||||||
|
|
||||||
|
loop.enterLoopMSecs(500);
|
||||||
|
if (loop.timeout())
|
||||||
|
QSKIP("Failed to run the task, nothing to test");
|
||||||
|
|
||||||
|
gotcha = watcha.future();
|
||||||
|
testTakeResults(gotcha, size_type(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::resultsReadyAt_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<bool>("testMove");
|
||||||
|
|
||||||
|
QTest::addRow("reportResult") << false;
|
||||||
|
QTest::addRow("reportAndMoveResult") << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::resultsReadyAt()
|
||||||
|
{
|
||||||
|
QFETCH(const bool, testMove);
|
||||||
|
|
||||||
|
QFutureInterface<int> iface;
|
||||||
|
QFutureWatcher<int> watcher;
|
||||||
|
watcher.setFuture(iface.future());
|
||||||
|
|
||||||
|
QTestEventLoop eventProcessor;
|
||||||
|
connect(&watcher, &QFutureWatcher<int>::finished, &eventProcessor, &QTestEventLoop::exitLoop);
|
||||||
|
|
||||||
|
const int nExpectedResults = 4;
|
||||||
|
int reported = 0;
|
||||||
|
int taken = 0;
|
||||||
|
connect(&watcher, &QFutureWatcher<int>::resultsReadyAt,
|
||||||
|
[&iface, &reported, &taken](int begin, int end)
|
||||||
|
{
|
||||||
|
auto future = iface.future();
|
||||||
|
QVERIFY(end - begin > 0);
|
||||||
|
for (int i = begin; i < end; ++i, ++reported) {
|
||||||
|
QVERIFY(future.isResultReadyAt(i));
|
||||||
|
taken |= 1 << i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto report = [&iface, testMove](int index)
|
||||||
|
{
|
||||||
|
int dummyResult = 0b101010;
|
||||||
|
if (testMove)
|
||||||
|
iface.reportAndMoveResult(std::move(dummyResult), index);
|
||||||
|
else
|
||||||
|
iface.reportResult(&dummyResult, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const QSignalSpy readyCounter(&watcher, &QFutureWatcher<int>::resultsReadyAt);
|
||||||
|
QTimer::singleShot(0, [&iface, &report]{
|
||||||
|
// With filter mode == true, the result may go into the pending results.
|
||||||
|
// Reporting it as ready will allow an application to try and access the
|
||||||
|
// result, crashing on invalid (store.end()) iterator dereferenced.
|
||||||
|
iface.setFilterMode(true);
|
||||||
|
iface.reportStarted();
|
||||||
|
report(0);
|
||||||
|
report(1);
|
||||||
|
// This one - should not be reported (it goes into pending):
|
||||||
|
report(3);
|
||||||
|
// Let's close the 'gap' and make them all ready:
|
||||||
|
report(-1);
|
||||||
|
iface.reportFinished();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run event loop, QCoreApplication::postEvent is in use
|
||||||
|
// in QFutureInterface:
|
||||||
|
eventProcessor.enterLoopMSecs(2000);
|
||||||
|
QVERIFY(!eventProcessor.timeout());
|
||||||
|
if (QTest::currentTestFailed()) // Failed in our lambda observing 'ready at'
|
||||||
|
return;
|
||||||
|
|
||||||
|
QCOMPARE(reported, nExpectedResults);
|
||||||
|
QCOMPARE(nExpectedResults, iface.future().resultCount());
|
||||||
|
QCOMPARE(readyCounter.count(), 3);
|
||||||
|
QCOMPARE(taken, 0b1111);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QFuture)
|
QTEST_MAIN(tst_QFuture)
|
||||||
#include "tst_qfuture.moc"
|
#include "tst_qfuture.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user