Add QPromise implementation

QPromise and QFuture created from it share the same internal state
(namely, QFutureInterface object). QPromise provides high-level
management of the shared resource, ensuring thread-safe behavior
on construction and destruction (also taking into account
QFuture::waitForFinished() semantics).

QFuture acts as a primary controller of QPromise via action
initiating methods such as suspend() or cancel(). QPromise is
equipped with methods to check the status, but the actual
handling of QFuture action "requests" is user-defined.

[ChangeLog][QtCore][QPromise] Added QPromise class to accompany
QFuture. It allows one to communicate computation results and
progress to the QFuture via a shared state.

Task-number: QTBUG-81586
Change-Id: Ibab9681d35fe63754bf394ad0e7923e2683e2457
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Andrei Golubev 2020-05-14 14:02:31 +03:00
parent 20ba86262f
commit 385f0732d9
10 changed files with 775 additions and 2 deletions

View File

@ -66,6 +66,10 @@ enum class Launch { Sync, Async, Inherit };
namespace QtPrivate {
template<class T, class U>
using EnableIfSameOrConvertible = std::enable_if_t<std::is_same_v<T, U>
|| std::is_convertible_v<T, U>>;
template<class T>
using EnableForVoid = std::enable_if_t<std::is_same_v<T, void>>;

View File

@ -259,6 +259,37 @@ void QFutureInterfaceBase::waitForResume()
d->pausedWaitCondition.wait(&d->m_mutex);
}
void QFutureInterfaceBase::suspendIfRequested()
{
const auto canSuspend = [] (int state) {
// can suspend only if 1) in any suspend-related state; 2) not canceled
return (state & suspendingOrSuspended) && !(state & Canceled);
};
// return early if possible to avoid taking the mutex lock.
{
const int state = d->state.loadRelaxed();
if (!canSuspend(state))
return;
}
QMutexLocker lock(&d->m_mutex);
const int state = d->state.loadRelaxed();
if (!canSuspend(state))
return;
// Note: expecting that Suspending and Suspended are mutually exclusive
if (!(state & Suspended)) {
// switch state in case this is the first invocation
switch_from_to(d->state, Suspending, Suspended);
d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Suspended));
}
// decrease active thread count since this thread will wait.
const ThreadPoolThreadReleaser releaser(d->pool());
d->pausedWaitCondition.wait(&d->m_mutex);
}
int QFutureInterfaceBase::progressValue() const
{
const QMutexLocker lock(&d->m_mutex);
@ -361,6 +392,11 @@ bool QFutureInterfaceBase::queryState(State state) const
return d->state.loadRelaxed() & state;
}
int QFutureInterfaceBase::loadState() const
{
return d->state.loadRelaxed();
}
void QFutureInterfaceBase::waitForResult(int resultIndex)
{
d->m_exceptionStore.throwPossibleException();

View File

@ -140,6 +140,7 @@ public:
bool isThrottled() const;
bool isResultReadyAt(int index) const;
bool isValid() const;
int loadState() const;
void cancel();
void setSuspended(bool suspend);
@ -151,6 +152,7 @@ public:
bool waitForNextResult();
void waitForResult(int resultIndex);
void waitForResume();
void suspendIfRequested();
QMutex &mutex() const;
QtPrivate::ExceptionStore &exceptionStore();
@ -233,6 +235,7 @@ public:
inline void reportResult(const T *result, int index = -1);
inline void reportAndMoveResult(T &&result, int index = -1);
inline void reportResult(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 reportFinished(const T *result);
@ -285,6 +288,12 @@ void QFutureInterface<T>::reportAndMoveResult(T &&result, int index)
reportResultsReady(insertIndex, store.count());
}
template<typename T>
void QFutureInterface<T>::reportResult(T &&result, int index)
{
reportAndMoveResult(std::move(result), index);
}
template <typename T>
inline void QFutureInterface<T>::reportResult(const T &result, int index)
{

View File

@ -0,0 +1,128 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QPROMISE_H
#define QPROMISE_H
#include <QtCore/qglobal.h>
#include <QtCore/qfuture.h>
QT_REQUIRE_CONFIG(future);
QT_BEGIN_NAMESPACE
template<typename T>
class QPromise
{
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:
QPromise() = default;
Q_DISABLE_COPY(QPromise)
QPromise(QPromise<T> &&other) : d(other.d)
{
// In constructor, there's no need to perform swap(). Assign new
// QFutureInterface to other.d instead which is slightly cheaper.
other.d = QFutureInterface<T>();
}
QPromise& operator=(QPromise<T> &&other)
{
swap(other);
return *this;
}
~QPromise()
{
const int state = d.loadState();
// If QFutureInterface has no state, there is nothing to be done
if (state == static_cast<int>(QFutureInterfaceBase::State::NoState))
return;
// Otherwise, if computation is not finished at this point, cancel
// potential waits
if (!(state & QFutureInterfaceBase::State::Finished)) {
d.cancel();
reportFinished(); // required to finalize the state
}
}
// Core QPromise APIs
QFuture<T> future() const { return d.future(); }
template<typename U = T,
typename = QtPrivate::EnableForNonVoid<std::decay_t<U>>,
typename = QtPrivate::EnableIfSameOrConvertible<std::decay_t<U>, std::decay_t<T>>>
void addResult(U &&result, int index = -1)
{
d.reportResult(std::forward<U>(result), index);
}
#ifndef QT_NO_EXCEPTIONS
void setException(const QException &e) { d.reportException(e); }
void setException(std::exception_ptr e) { d.reportException(e); }
#endif
void reportStarted() { d.reportStarted(); }
void reportFinished() { d.reportFinished(); }
void suspendIfRequested() { d.suspendIfRequested(); }
bool isCanceled() const { return d.isCanceled(); }
// Progress methods
void setProgressRange(int minimum, int maximum) { d.setProgressRange(minimum, maximum); }
void setProgressValue(int progressValue) { d.setProgressValue(progressValue); }
void setProgressValueAndText(int progressValue, const QString &progressText)
{
d.setProgressValueAndText(progressValue, progressText);
}
private:
mutable QFutureInterface<T> d = QFutureInterface<T>();
void swap(QPromise<T> &other)
{
// Note: copy operations are expensive! They trigger several atomic
// reference counts
auto tmp = this->d;
this->d = other.d;
other.d = tmp;
}
};
QT_END_NAMESPACE
#endif // QPROMISE_H

View File

@ -72,7 +72,8 @@ qtConfig(future) {
thread/qfuturesynchronizer.h \
thread/qfuturewatcher.h \
thread/qfuturewatcher_p.h \
thread/qresultstore.h
thread/qresultstore.h \
thread/qpromise.h
SOURCES += \
thread/qexception.cpp \

View File

@ -18,6 +18,7 @@ if(QT_FEATURE_thread)
add_subdirectory(qthreadstorage)
add_subdirectory(qwaitcondition)
add_subdirectory(qwritelocker)
add_subdirectory(qpromise)
endif()
if(TARGET Qt::Concurrent)
add_subdirectory(qfuturewatcher)

View File

@ -0,0 +1,12 @@
#####################################################################
## tst_qpromise Test:
#####################################################################
qt_add_test(tst_qpromise
SOURCES
tst_qpromise.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
LIBRARIES
Threads::Threads # solves issue with libpthread linkage
)

View File

@ -0,0 +1,4 @@
CONFIG += testcase
TARGET = tst_qpromise
QT = core core-private testlib
SOURCES = tst_qpromise.cpp

View File

@ -0,0 +1,577 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QCoreApplication>
#include <QDebug>
#define QPROMISE_TEST
#include <QtTest/QtTest>
#include <qfuture.h>
#include <qfuturewatcher.h>
#include <qpromise.h>
#include <algorithm>
#include <memory>
#include <chrono>
class tst_QPromise : public QObject
{
Q_OBJECT
private slots:
// simple test cases
void promise();
void futureFromPromise();
void addResult();
void addResultOutOfOrder();
#ifndef QT_NO_EXCEPTIONS
void setException();
#endif
void cancel();
void progress();
// complicated test cases
void addInThread();
void addInThreadMoveOnlyObject(); // separate test case - QTBUG-84736
void reportFromMultipleThreads();
void reportFromMultipleThreadsByMovedPromise();
void doNotCancelWhenFinished();
#ifndef QT_NO_EXCEPTIONS
void cancelWhenDestroyed();
#endif
void cancelWhenReassigned();
void finishWhenMoved();
void cancelWhenMoved();
void waitUntilResumed();
void waitUntilCanceled();
};
struct TrivialType { int field = 0; };
struct CopyOnlyType {
Q_DISABLE_MOVE(CopyOnlyType);
CopyOnlyType() = default;
CopyOnlyType(const CopyOnlyType &) = default;
CopyOnlyType& operator=(const CopyOnlyType &) = default;
~CopyOnlyType() = default;
int field = 0;
};
struct MoveOnlyType {
Q_DISABLE_COPY(MoveOnlyType);
MoveOnlyType() = default;
MoveOnlyType(MoveOnlyType &&) = default;
MoveOnlyType& operator=(MoveOnlyType &&) = default;
~MoveOnlyType() = default;
int field = 0;
};
bool operator==(const CopyOnlyType &a, const CopyOnlyType &b) { return a.field == b.field; }
bool operator==(const MoveOnlyType &a, const MoveOnlyType &b) { return a.field == b.field; }
// A wrapper for a test function, calls the function, if it fails, reports failure
#define RUN_TEST_FUNC(test, ...) \
do { \
test(__VA_ARGS__); \
if (QTest::currentTestFailed()) \
QFAIL("Test case " #test "(" #__VA_ARGS__ ") failed"); \
} while (false)
// std::thread-like wrapper that ensures that the thread is joined at the end of
// a scope to prevent potential std::terminate
struct ThreadWrapper
{
std::unique_ptr<QThread> t;
template<typename Function>
ThreadWrapper(Function &&f) : t(QThread::create(std::forward<Function>(f)))
{
t->start();
}
void join() { t->wait(); }
~ThreadWrapper()
{
t->wait();
}
};
void tst_QPromise::promise()
{
const auto testCanCreatePromise = [] (auto promise) {
promise.reportStarted();
promise.suspendIfRequested(); // should not block on its own
promise.reportFinished();
};
RUN_TEST_FUNC(testCanCreatePromise, QPromise<void>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<int>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<QList<float>>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<TrivialType>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<CopyOnlyType>());
RUN_TEST_FUNC(testCanCreatePromise, QPromise<MoveOnlyType>());
}
void tst_QPromise::futureFromPromise()
{
const auto testCanCreateFutureFromPromise = [] (auto promise) {
auto future = promise.future();
QVERIFY(!future.isValid());
promise.reportStarted();
QCOMPARE(future.isStarted(), true);
QVERIFY(future.isValid());
promise.reportFinished();
QCOMPARE(future.isFinished(), true);
QVERIFY(future.isValid());
future.waitForFinished();
};
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<void>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<double>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<QList<int>>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<TrivialType>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<CopyOnlyType>());
RUN_TEST_FUNC(testCanCreateFutureFromPromise, QPromise<MoveOnlyType>());
}
void tst_QPromise::addResult()
{
QPromise<int> promise;
auto f = promise.future();
// add as lvalue
{
int result = 456;
promise.addResult(result);
QCOMPARE(f.resultCount(), 1);
QCOMPARE(f.result(), result);
QCOMPARE(f.resultAt(0), result);
}
// add as rvalue
{
int result = 789;
promise.addResult(789);
QCOMPARE(f.resultCount(), 2);
QCOMPARE(f.resultAt(1), result);
}
// add at position
{
int result = 56238;
promise.addResult(result, 2);
QCOMPARE(f.resultCount(), 3);
QCOMPARE(f.resultAt(2), result);
}
// add at position and overwrite
{
int result = -1;
const auto originalCount = f.resultCount();
promise.addResult(result, 0);
QCOMPARE(f.resultCount(), originalCount);
QCOMPARE(f.resultAt(0), result);
}
}
void tst_QPromise::addResultOutOfOrder()
{
// Compare results available in QFuture to expected results
const auto compareResults = [] (const auto &future, auto expected) {
QCOMPARE(future.resultCount(), expected.size());
// index based loop
for (int i = 0; i < future.resultCount(); ++i)
QCOMPARE(future.resultAt(i), expected.at(i));
// iterator based loop
QVERIFY(std::equal(future.begin(), future.end(), expected.begin()));
};
// out of order results without a gap
{
QPromise<int> promise;
auto f = promise.future();
promise.addResult(456, 1);
QCOMPARE(f.resultCount(), 0);
promise.addResult(123, 0);
QList<int> expected({123, 456});
RUN_TEST_FUNC(compareResults, f, expected);
QCOMPARE(f.results(), expected);
}
// out of order results with a gap that is closed "later"
{
QPromise<int> promise;
auto f = promise.future();
promise.addResult(0, 0);
promise.addResult(1, 1);
promise.addResult(3, 3); // intentional gap here
QList<int> expectedWhenGapExists({0, 1});
RUN_TEST_FUNC(compareResults, f, expectedWhenGapExists);
QCOMPARE(f.resultAt(3), 3);
QList<int> expectedWhenNoGap({0, 1, 2, 3});
promise.addResult(2, 2); // fill a gap with a value
RUN_TEST_FUNC(compareResults, f, expectedWhenNoGap);
QCOMPARE(f.results(), expectedWhenNoGap);
}
}
#ifndef QT_NO_EXCEPTIONS
void tst_QPromise::setException()
{
struct TestException {}; // custom exception class
const auto testExceptionCaught = [] (auto promise, const auto& exception) {
auto f = promise.future();
promise.reportStarted();
promise.setException(exception);
promise.reportFinished();
bool caught = false;
try {
f.waitForFinished();
} catch (const QException&) {
caught = true;
} catch (const TestException&) {
caught = true;
}
QVERIFY(caught);
};
RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(), QException());
RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(), QException());
RUN_TEST_FUNC(testExceptionCaught, QPromise<void>(),
std::make_exception_ptr(TestException()));
RUN_TEST_FUNC(testExceptionCaught, QPromise<int>(),
std::make_exception_ptr(TestException()));
}
#endif
void tst_QPromise::cancel()
{
const auto testCancel = [] (auto promise) {
auto f = promise.future();
f.cancel();
QCOMPARE(promise.isCanceled(), true);
};
testCancel(QPromise<void>());
testCancel(QPromise<int>());
}
void tst_QPromise::progress()
{
const auto testProgress = [] (auto promise) {
auto f = promise.future();
promise.setProgressRange(0, 2);
QCOMPARE(f.progressMinimum(), 0);
QCOMPARE(f.progressMaximum(), 2);
QCOMPARE(f.progressValue(), 0);
promise.setProgressValue(1);
QCOMPARE(f.progressValue(), 1);
promise.setProgressValue(0); // decrement
QCOMPARE(f.progressValue(), 1);
promise.setProgressValue(10); // out of range
QEXPECT_FAIL("", "Out of range value is set - QTBUG-84729", Continue);
QCOMPARE(f.progressValue(), 1);
promise.setProgressRange(0, 100);
promise.setProgressValueAndText(50, u8"50%");
QCOMPARE(f.progressValue(), 50);
QCOMPARE(f.progressText(), u8"50%");
};
RUN_TEST_FUNC(testProgress, QPromise<void>());
RUN_TEST_FUNC(testProgress, QPromise<int>());
}
void tst_QPromise::addInThread()
{
const auto testAddResult = [] (auto promise, const auto &result) {
promise.reportStarted();
auto f = promise.future();
// move construct QPromise
ThreadWrapper thr([p = std::move(promise), &result] () mutable {
p.addResult(result);
});
// Waits for result first
QCOMPARE(f.result(), result);
QCOMPARE(f.resultAt(0), result);
};
RUN_TEST_FUNC(testAddResult, QPromise<int>(), 42);
RUN_TEST_FUNC(testAddResult, QPromise<QString>(), u8"42");
RUN_TEST_FUNC(testAddResult, QPromise<CopyOnlyType>(), CopyOnlyType{99});
}
void tst_QPromise::addInThreadMoveOnlyObject()
{
QPromise<MoveOnlyType> promise;
promise.reportStarted();
auto f = promise.future();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.addResult(MoveOnlyType{-11});
});
// Iterators wait for result first
for (auto& result : f)
QCOMPARE(result, MoveOnlyType{-11});
}
void tst_QPromise::reportFromMultipleThreads()
{
QPromise<int> promise;
auto f = promise.future();
promise.reportStarted();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
promise.reportFinished();
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
}
void tst_QPromise::reportFromMultipleThreadsByMovedPromise()
{
QPromise<int> initialPromise;
auto f = initialPromise.future();
{
// Move QPromise into local scope: local QPromise (as being
// move-constructed) must be able to set results, QFuture must still
// hold correct references to results.
auto promise = std::move(initialPromise);
promise.reportStarted();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
promise.reportFinished();
}
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isValid(), true);
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
}
void tst_QPromise::doNotCancelWhenFinished()
{
const auto testFinishedPromise = [] (auto promise) {
auto f = promise.future();
promise.reportStarted();
// Finish QPromise inside thread, destructor must not call cancel()
ThreadWrapper([p = std::move(promise)] () mutable { p.reportFinished(); }).join();
f.waitForFinished();
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), false);
};
RUN_TEST_FUNC(testFinishedPromise, QPromise<void>());
RUN_TEST_FUNC(testFinishedPromise, QPromise<int>());
RUN_TEST_FUNC(testFinishedPromise, QPromise<QString>());
}
#ifndef QT_NO_EXCEPTIONS
void tst_QPromise::cancelWhenDestroyed()
{
QPromise<int> initialPromise;
auto f = initialPromise.future();
try {
// Move QPromise to local scope. On destruction, it must call cancel().
auto promise = std::move(initialPromise);
promise.reportStarted();
ThreadWrapper threads[] = {
ThreadWrapper([&promise] () mutable { promise.addResult(42); }),
ThreadWrapper([&promise] () mutable { promise.addResult(43); }),
ThreadWrapper([&promise] () mutable { promise.addResult(44); }),
};
for (auto& t : threads)
t.join();
throw "Throw in the middle, we lose our promise here, reportFinished() not called!";
promise.reportFinished();
} catch (...) {}
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), true);
// Results are still available despite throw
QList<int> expected = {42, 43, 44};
for (auto actual : f.results()) {
QVERIFY(std::find(expected.begin(), expected.end(), actual) != expected.end());
expected.removeOne(actual);
}
}
#endif
void tst_QPromise::cancelWhenReassigned()
{
QPromise<int> promise;
auto f = promise.future();
promise.reportStarted();
ThreadWrapper thr([p = std::move(promise)] () mutable {
QThread::msleep(100);
p = QPromise<int>(); // assign new promise, old must be correctly destroyed
});
f.waitForFinished(); // wait for the old promise
QCOMPARE(f.isFinished(), true);
QCOMPARE(f.isCanceled(), true);
}
void tst_QPromise::finishWhenMoved()
{
QPromise<int> promise1;
auto f1 = promise1.future();
promise1.reportStarted();
QPromise<int> promise2;
auto f2 = promise2.future();
promise2.reportStarted();
ThreadWrapper thr([&promise1, &promise2] () mutable {
QThread::msleep(100);
// There is swap semantics in move, so promise #1 and #2 just swap
promise1 = std::move(promise2);
promise1.reportFinished(); // this finish is for future #2
promise2.reportFinished(); // this finish is for future #1
});
f1.waitForFinished();
f2.waitForFinished();
// Future #1 and #2 are finished inside thread
QCOMPARE(f1.isFinished(), true);
QCOMPARE(f1.isCanceled(), false);
QCOMPARE(f2.isFinished(), true);
QCOMPARE(f2.isCanceled(), false);
}
void tst_QPromise::cancelWhenMoved()
{
QPromise<int> promise1;
auto f1 = promise1.future();
promise1.reportStarted();
QPromise<int> promise2;
auto f2 = promise2.future();
promise2.reportStarted();
// Move promises to local scope to test cancellation behavior
ThreadWrapper thr([p1 = std::move(promise1), p2 = std::move(promise2)] () mutable {
QThread::msleep(100);
// There is swap semantics in move, so promise #1 and #2 just swap
p1 = std::move(p2);
p1.reportFinished(); // this finish is for future #2
});
f1.waitForFinished();
f2.waitForFinished();
// Future #1 is implicitly cancelled inside thread
QCOMPARE(f1.isFinished(), true);
QCOMPARE(f1.isCanceled(), true);
// Future #2 is explicitly finished inside thread
QCOMPARE(f2.isFinished(), true);
QCOMPARE(f2.isCanceled(), false);
}
void tst_QPromise::waitUntilResumed()
{
QPromise<int> promise;
promise.reportStarted();
auto f = promise.future();
f.suspend();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.suspendIfRequested();
p.addResult(42); // result added after suspend
p.reportFinished();
});
while (!f.isSuspended()) { // busy wait until worker thread suspends
QCOMPARE(f.isFinished(), false); // exit condition in case of failure
QThread::msleep(50); // allow another thread to actually carry on
}
f.resume();
f.waitForFinished();
QCOMPARE(f.resultCount(), 1);
QCOMPARE(f.result(), 42);
}
void tst_QPromise::waitUntilCanceled()
{
QPromise<int> promise;
promise.reportStarted();
auto f = promise.future();
f.suspend();
ThreadWrapper thr([p = std::move(promise)] () mutable {
p.suspendIfRequested();
p.addResult(42); // result not added due to QFuture::cancel()
p.reportFinished();
});
while (!f.isSuspended()) { // busy wait until worker thread suspends
QCOMPARE(f.isFinished(), false); // exit condition in case of failure
QThread::msleep(50); // allow another thread to actually carry on
}
f.cancel();
f.waitForFinished();
QCOMPARE(f.resultCount(), 0);
}
QTEST_MAIN(tst_QPromise)
#include "tst_qpromise.moc"

View File

@ -18,7 +18,8 @@ qtConfig(thread) {
qthreadpool \
qthreadstorage \
qwaitcondition \
qwritelocker
qwritelocker \
qpromise
}
qtHaveModule(concurrent) {