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 // handle other exceptions
}); });
//! [14] //! [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); QFuture<T> onFailed(Function &&handler);
#endif #endif
template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>>
QFuture<T> onCanceled(Function &&handler);
class const_iterator class const_iterator
{ {
public: public:
@ -281,6 +284,9 @@ private:
template<class Function, class ResultType, class ParentResultType> template<class Function, class ResultType, class ParentResultType>
friend class QtPrivate::Continuation; friend class QtPrivate::Continuation;
template<class Function, class ResultType>
friend class QtPrivate::CanceledHandler;
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType> template<class Function, class ResultType>
friend class QtPrivate::FailureHandler; friend class QtPrivate::FailureHandler;
@ -359,6 +365,15 @@ QFuture<T> QFuture<T>::onFailed(Function &&handler)
#endif #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() inline QFuture<void> QFutureInterface<void>::future()
{ {
return QFuture<void>(this); return QFuture<void>(this);

View File

@ -55,8 +55,9 @@
results in the future. results in the future.
If the result of one asynchronous computation needs to be passed If the result of one asynchronous computation needs to be passed
to another, QFuture provides a convenient way of chaining multiple to another, QFuture provides a convenient way of chaining multiple sequential
sequential computations using then(). Additionally, onFailed() can be used 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 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, 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 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 \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 QFuture also offers ways to interact with a runnning computation. For
instance, the computation can be canceled with the cancel() function. To instance, the computation can be canceled with the cancel() function. To
pause the computation, use the setPaused() function or one of the pause(), 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 \note If the parent future gets canceled, its continuations will
also be canceled. 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) /*! \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){ ... }); .then(QtFuture::Launch::Inherit, [](int res2){ ... });
\endcode \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) /*! \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 future finishes, \a function will be invoked in a separate thread taken from the
QThreadPool \a pool. QThreadPool \a pool.
\sa onFailed() \sa onFailed(), onCanceled()
*/ */
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler) /*! \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 \note You can always attach a handler taking no argument, to handle all exception
types and avoid writing the try-catch block. 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)...)); 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 #ifndef QT_NO_EXCEPTIONS
template<class Function, class ResultType> template<class Function, class ResultType>
@ -529,12 +551,7 @@ void FailureHandler<Function, ResultType>::run()
handleException<ArgType>(); handleException<ArgType>();
} }
} else { } else {
if constexpr (!std::is_void_v<ResultType>) { QtPrivate::fulfillPromise(promise, parentFuture);
if constexpr (std::is_copy_constructible_v<ResultType>)
promise.reportResult(parentFuture.result());
else
promise.reportAndMoveResult(parentFuture.takeResult());
}
} }
promise.reportFinished(); promise.reportFinished();
} }
@ -573,12 +590,7 @@ void FailureHandler<Function, ResultType>::handleAllExceptions()
parentFuture.d.exceptionStore().throwPossibleException(); parentFuture.d.exceptionStore().throwPossibleException();
} catch (...) { } catch (...) {
try { try {
if constexpr (std::is_void_v<ResultType>) QtPrivate::fulfillPromise(promise, std::forward<Function>(handler));
handler();
else if constexpr (std::is_copy_constructible_v<ResultType>)
promise.reportResult(handler());
else
promise.reportAndMoveResult(handler());
} catch (...) { } catch (...) {
promise.reportException(std::current_exception()); promise.reportException(std::current_exception());
} }
@ -587,6 +599,45 @@ void FailureHandler<Function, ResultType>::handleAllExceptions()
#endif // QT_NO_EXCEPTIONS #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 QtPrivate
namespace QtFuture { namespace QtFuture {

View File

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

View File

@ -131,6 +131,7 @@ private slots:
void onFailedTestCallables(); void onFailedTestCallables();
void onFailedForMoveOnlyTypes(); void onFailedForMoveOnlyTypes();
#endif #endif
void onCanceled();
void takeResults(); void takeResults();
void takeResult(); void takeResult();
void runAndTake(); void runAndTake();
@ -1933,18 +1934,24 @@ void tst_QFuture::thenForMoveOnlyTypes()
QVERIFY(runThenForMoveOnly<void>([] { return std::make_unique<int>(42); })); 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() void tst_QFuture::thenOnCanceledFuture()
{ {
// Continuations on a canceled future // Continuations on a canceled future
{ {
QFutureInterface<void> promise;
promise.reportStarted();
promise.reportCanceled();
promise.reportFinished();
int thenResult = 0; int thenResult = 0;
QFuture<void> then = QFuture<void> then = createCanceledFuture<void>().then([&]() { ++thenResult; }).then([&]() {
promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; }); ++thenResult;
});
QVERIFY(then.isCanceled()); QVERIFY(then.isCanceled());
QCOMPARE(thenResult, 0); QCOMPARE(thenResult, 0);
@ -1970,16 +1977,10 @@ void tst_QFuture::thenOnCanceledFuture()
// Continuations on a canceled future // Continuations on a canceled future
{ {
QFutureInterface<void> promise;
promise.reportStarted();
promise.reportCanceled();
promise.reportFinished();
int thenResult = 0; int thenResult = 0;
QFuture<void> then = QFuture<void> then = createCanceledFuture<void>()
promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() { .then(QtFuture::Launch::Async, [&]() { ++thenResult; })
++thenResult; .then([&]() { ++thenResult; });
});
QVERIFY(then.isCanceled()); QVERIFY(then.isCanceled());
QCOMPARE(thenResult, 0); QCOMPARE(thenResult, 0);
@ -2486,6 +2487,15 @@ void tst_QFuture::onFailed()
QCOMPARE(checkpoint, 3); QCOMPARE(checkpoint, 3);
QCOMPARE(res, 0); 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> template<class Callable>
@ -2565,6 +2575,84 @@ void tst_QFuture::onFailedForMoveOnlyTypes()
#endif // QT_NO_EXCEPTIONS #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) void tst_QFuture::testSingleResult(const UniquePtr &p)
{ {
QVERIFY(p.get() != nullptr); QVERIFY(p.get() != nullptr);