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:
parent
612f6999c8
commit
1a5cc8d13d
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user