Add support of cancellation handler callbacks to QFuture

Added QFuture::onCanceled() method, for attaching handlers to be called
when the QFuture gets canceled.

Change-Id: I1f01647d6173ba0c1db6641e14140108b33ac7c4
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Sona Kurazyan 2020-05-05 17:44:47 +02:00
parent 612f6999c8
commit 1a5cc8d13d
6 changed files with 263 additions and 34 deletions

View File

@ -220,3 +220,35 @@ QtFuture::connect(&object, &Object::singleArgSignal).then([](int value) {
// handle other exceptions
});
//! [14]
//! [15]
QFuture<int> testFuture = ...;
auto resultFuture = testFuture.then([](int res) {
// Block 1
}).onCanceled([] {
// Block 2
}).onFailed([] {
// Block 3
}).then([] {
// Block 4
}).onFailed([] {
// Block 5
}).onCanceled([] {
// Block 6
});
//! [15]
//! [16]
QFuture<int> testFuture = ...;
auto resultFuture = testFuture.then([](int res) {
// Block 1
}).onFailed([] {
// Block 3
}).then([] {
// Block 4
}).onFailed([] {
// Block 5
}).onCanceled([] {
// Block 6
});
//! [16]

View File

@ -168,6 +168,9 @@ public:
QFuture<T> onFailed(Function &&handler);
#endif
template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>>
QFuture<T> onCanceled(Function &&handler);
class const_iterator
{
public:
@ -281,6 +284,9 @@ private:
template<class Function, class ResultType, class ParentResultType>
friend class QtPrivate::Continuation;
template<class Function, class ResultType>
friend class QtPrivate::CanceledHandler;
#ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType>
friend class QtPrivate::FailureHandler;
@ -359,6 +365,15 @@ QFuture<T> QFuture<T>::onFailed(Function &&handler)
#endif
template<class T>
template<class Function, typename>
QFuture<T> QFuture<T>::onCanceled(Function &&handler)
{
QFutureInterface<T> promise(QFutureInterfaceBase::State::Pending);
QtPrivate::CanceledHandler<Function, T>::create(std::forward<Function>(handler), this, promise);
return promise.future();
}
inline QFuture<void> QFutureInterface<void>::future()
{
return QFuture<void>(this);

View File

@ -55,8 +55,9 @@
results in the future.
If the result of one asynchronous computation needs to be passed
to another, QFuture provides a convenient way of chaining multiple
sequential computations using then(). Additionally, onFailed() can be used
to another, QFuture provides a convenient way of chaining multiple sequential
computations using then(). onCanceled() can be used for adding a handler to
be called if the QFuture is canceled. Additionally, onFailed() can be used
to handle any failures that occurred in the chain. Note that QFuture relies
on exceptions for the error handling. If using exceptions is not an option,
you can still indicate the error state of QFuture, by making the error type
@ -76,6 +77,31 @@
\snippet code/src_corelib_thread_qfuture.cpp 4
It's possible to chain multiple continuations and handlers in any order.
The first handler that can handle the state of its parent is invoked first.
If there's no proper handler, the state is propagated to the next continuation
or handler. For example:
\snippet code/src_corelib_thread_qfuture.cpp 15
If \c testFuture is successfully fulfilled \c {Block 1} will be called. If
it succeeds as well, the next then() (\c {Block 4}) is called. If \c testFuture
gets canceled or fails with an exception, either \c {Block 2} or \c {Block 3}
will be called respectively. The next then() will be called afterwards, and the
story repeats.
\note If \c {Block 2} is invoked and throws an exception, the following
onFailed() (\c {Block 3}) will handle it. If the order of onFailed() and
onCanceled() were reversed, the exception state would propagate to the
next continuations and eventually would be caught in \c {Block 5}.
In the next example the first onCanceled() (\c {Block 2}) is removed:
\snippet code/src_corelib_thread_qfuture.cpp 16
If \c testFuture gets canceled, its state is propagated to the next then(),
which will be also canceled. So in this case \c {Block 6} will be called.
QFuture also offers ways to interact with a runnning computation. For
instance, the computation can be canceled with the cancel() function. To
pause the computation, use the setPaused() function or one of the pause(),
@ -893,7 +919,7 @@
\note If the parent future gets canceled, its continuations will
also be canceled.
\sa onFailed()
\sa onFailed(), onCanceled()
*/
/*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(QtFuture::Launch policy, Function &&function)
@ -926,7 +952,7 @@
.then(QtFuture::Launch::Inherit, [](int res2){ ... });
\endcode
\sa onFailed()
\sa onFailed(), onCanceled()
*/
/*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(QThreadPool *pool, Function &&function)
@ -939,7 +965,7 @@
future finishes, \a function will be invoked in a separate thread taken from the
QThreadPool \a pool.
\sa onFailed()
\sa onFailed(), onCanceled()
*/
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler)
@ -970,5 +996,16 @@
\note You can always attach a handler taking no argument, to handle all exception
types and avoid writing the try-catch block.
\sa then()
\sa then(), onCanceled()
*/
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onCanceled(Function &&handler)
\since 6.0
Attaches a cancellation \a handler to this future, to be called when the future is
canceled. The \a handler is a callable which doesn't take any arguments. It will be
invoked in the same thread in which this future has been running.
\sa then(), onFailed()
*/

View File

@ -495,6 +495,28 @@ void Continuation<Function, ResultType, ParentResultType>::fulfillPromise(Args &
promise.reportAndMoveResult(std::invoke(function, std::forward<Args>(args)...));
}
template<class T>
void fulfillPromise(QFutureInterface<T> &promise, QFuture<T> &future)
{
if constexpr (!std::is_void_v<T>) {
if constexpr (std::is_copy_constructible_v<T>)
promise.reportResult(future.result());
else
promise.reportAndMoveResult(future.takeResult());
}
}
template<class T, class Function>
void fulfillPromise(QFutureInterface<T> &promise, Function &&handler)
{
if constexpr (std::is_void_v<T>)
handler();
else if constexpr (std::is_copy_constructible_v<T>)
promise.reportResult(handler());
else
promise.reportAndMoveResult(handler());
}
#ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType>
@ -529,12 +551,7 @@ void FailureHandler<Function, ResultType>::run()
handleException<ArgType>();
}
} else {
if constexpr (!std::is_void_v<ResultType>) {
if constexpr (std::is_copy_constructible_v<ResultType>)
promise.reportResult(parentFuture.result());
else
promise.reportAndMoveResult(parentFuture.takeResult());
}
QtPrivate::fulfillPromise(promise, parentFuture);
}
promise.reportFinished();
}
@ -573,12 +590,7 @@ void FailureHandler<Function, ResultType>::handleAllExceptions()
parentFuture.d.exceptionStore().throwPossibleException();
} catch (...) {
try {
if constexpr (std::is_void_v<ResultType>)
handler();
else if constexpr (std::is_copy_constructible_v<ResultType>)
promise.reportResult(handler());
else
promise.reportAndMoveResult(handler());
QtPrivate::fulfillPromise(promise, std::forward<Function>(handler));
} catch (...) {
promise.reportException(std::current_exception());
}
@ -587,6 +599,45 @@ void FailureHandler<Function, ResultType>::handleAllExceptions()
#endif // QT_NO_EXCEPTIONS
template<class Function, class ResultType>
class CanceledHandler
{
public:
static QFuture<ResultType> create(Function &&handler, QFuture<ResultType> *future,
QFutureInterface<ResultType> promise)
{
Q_ASSERT(future);
auto canceledContinuation = [parentFuture = *future, promise,
handler = std::move(handler)]() mutable {
promise.reportStarted();
if (parentFuture.isCanceled()) {
#ifndef QT_NO_EXCEPTIONS
if (parentFuture.d.exceptionStore().hasException()) {
// Propagate the exception to the result future
promise.reportException(parentFuture.d.exceptionStore().exception());
} else {
try {
#endif
QtPrivate::fulfillPromise(promise, std::forward<Function>(handler));
#ifndef QT_NO_EXCEPTIONS
} catch (...) {
promise.reportException(std::current_exception());
}
}
#endif
} else {
QtPrivate::fulfillPromise(promise, parentFuture);
}
promise.reportFinished();
};
future->d.setContinuation(std::move(canceledContinuation));
return promise.future();
}
};
} // namespace QtPrivate
namespace QtFuture {

View File

@ -64,6 +64,9 @@ namespace QtPrivate {
template<typename Function, typename ResultType, typename ParentResultType>
class Continuation;
template<class Function, class ResultType>
class CanceledHandler;
#ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType>
class FailureHandler;
@ -163,6 +166,9 @@ private:
template<typename Function, typename ResultType, typename ParentResultType>
friend class QtPrivate::Continuation;
template<class Function, class ResultType>
friend class QtPrivate::CanceledHandler;
#ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType>
friend class QtPrivate::FailureHandler;

View File

@ -131,6 +131,7 @@ private slots:
void onFailedTestCallables();
void onFailedForMoveOnlyTypes();
#endif
void onCanceled();
void takeResults();
void takeResult();
void runAndTake();
@ -1933,18 +1934,24 @@ void tst_QFuture::thenForMoveOnlyTypes()
QVERIFY(runThenForMoveOnly<void>([] { return std::make_unique<int>(42); }));
}
template<class T>
QFuture<T> createCanceledFuture()
{
QFutureInterface<T> promise;
promise.reportStarted();
promise.reportCanceled();
promise.reportFinished();
return promise.future();
}
void tst_QFuture::thenOnCanceledFuture()
{
// Continuations on a canceled future
{
QFutureInterface<void> promise;
promise.reportStarted();
promise.reportCanceled();
promise.reportFinished();
int thenResult = 0;
QFuture<void> then =
promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; });
QFuture<void> then = createCanceledFuture<void>().then([&]() { ++thenResult; }).then([&]() {
++thenResult;
});
QVERIFY(then.isCanceled());
QCOMPARE(thenResult, 0);
@ -1970,16 +1977,10 @@ void tst_QFuture::thenOnCanceledFuture()
// Continuations on a canceled future
{
QFutureInterface<void> promise;
promise.reportStarted();
promise.reportCanceled();
promise.reportFinished();
int thenResult = 0;
QFuture<void> then =
promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() {
++thenResult;
});
QFuture<void> then = createCanceledFuture<void>()
.then(QtFuture::Launch::Async, [&]() { ++thenResult; })
.then([&]() { ++thenResult; });
QVERIFY(then.isCanceled());
QCOMPARE(thenResult, 0);
@ -2486,6 +2487,15 @@ void tst_QFuture::onFailed()
QCOMPARE(checkpoint, 3);
QCOMPARE(res, 0);
}
// onFailed on a canceled future
{
auto future = createCanceledFuture<int>()
.then([](int) { return 42; })
.onCanceled([] { return -1; })
.onFailed([] { return -2; });
QCOMPARE(future.result(), -1);
}
}
template<class Callable>
@ -2565,6 +2575,84 @@ void tst_QFuture::onFailedForMoveOnlyTypes()
#endif // QT_NO_EXCEPTIONS
void tst_QFuture::onCanceled()
{
// Canceled int future
{
auto future = createCanceledFuture<int>().then([](int) { return 42; }).onCanceled([] {
return -1;
});
QCOMPARE(future.result(), -1);
}
// Canceled void future
{
int checkpoint = 0;
auto future = createCanceledFuture<void>().then([&] { checkpoint = 42; }).onCanceled([&] {
checkpoint = -1;
});
QCOMPARE(checkpoint, -1);
}
// onCanceled propagates result
{
QFutureInterface<int> promise;
auto future =
promise.future().then([](int res) { return res; }).onCanceled([] { return -1; });
promise.reportStarted();
promise.reportResult(42);
promise.reportFinished();
QCOMPARE(future.result(), 42);
}
// onCanceled propagates move-only result
{
QFutureInterface<UniquePtr> promise;
auto future = promise.future().then([](UniquePtr res) { return res; }).onCanceled([] {
return std::make_unique<int>(-1);
});
promise.reportStarted();
promise.reportAndMoveResult(std::make_unique<int>(42));
promise.reportFinished();
QCOMPARE(*future.takeResult(), 42);
}
#ifndef QT_NO_EXCEPTIONS
// onCanceled propagates exceptions
{
QFutureInterface<int> promise;
auto future = promise.future()
.then([](int res) {
throw std::runtime_error("error");
return res;
})
.onCanceled([] { return 2; })
.onFailed([] { return 3; });
promise.reportStarted();
promise.reportResult(1);
promise.reportFinished();
QCOMPARE(future.result(), 3);
}
// onCanceled throws
{
auto future = createCanceledFuture<int>()
.then([](int) { return 42; })
.onCanceled([] {
throw std::runtime_error("error");
return -1;
})
.onFailed([] { return -2; });
QCOMPARE(future.result(), -2);
}
#endif // QT_NO_EXCEPTIONS
}
void tst_QFuture::testSingleResult(const UniquePtr &p)
{
QVERIFY(p.get() != nullptr);