Add support of failure handler callbacks to QFuture
Added QFuture::onFailed() method, which allows attaching handlers for exceptions that may occur in QFuture continuation chains. Task-number: QTBUG-81588 Change-Id: Iadeee99e3a7573207f6ca9f650ff9f7b6faa2cf7 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
495f958b9a
commit
0f78e85421
@ -162,6 +162,12 @@ public:
|
|||||||
template<class Function>
|
template<class Function>
|
||||||
QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function);
|
QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function);
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
template<class Function,
|
||||||
|
typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>>
|
||||||
|
QFuture<T> onFailed(Function &&handler);
|
||||||
|
#endif
|
||||||
|
|
||||||
class const_iterator
|
class const_iterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -275,6 +281,11 @@ private:
|
|||||||
template<class Function, class ResultType, class ParentResultType>
|
template<class Function, class ResultType, class ParentResultType>
|
||||||
friend class QtPrivate::Continuation;
|
friend class QtPrivate::Continuation;
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
friend class QtPrivate::FailureHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
using QFuturePrivate =
|
using QFuturePrivate =
|
||||||
std::conditional_t<std::is_same_v<T, void>, QFutureInterfaceBase, QFutureInterface<T>>;
|
std::conditional_t<std::is_same_v<T, void>, QFutureInterfaceBase, QFutureInterface<T>>;
|
||||||
|
|
||||||
@ -335,6 +346,19 @@ QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QTh
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
template<class Function, typename>
|
||||||
|
QFuture<T> QFuture<T>::onFailed(Function &&handler)
|
||||||
|
{
|
||||||
|
QFutureInterface<T> promise(QFutureInterfaceBase::State::Pending);
|
||||||
|
QtPrivate::FailureHandler<Function, T>::create(std::forward<Function>(handler), this, promise);
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
inline QFuture<void> QFutureInterface<void>::future()
|
inline QFuture<void> QFutureInterface<void>::future()
|
||||||
{
|
{
|
||||||
return QFuture<void>(this);
|
return QFuture<void>(this);
|
||||||
|
@ -846,6 +846,8 @@
|
|||||||
|
|
||||||
\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()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \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)
|
||||||
@ -877,6 +879,8 @@
|
|||||||
future.then(QtFuture::Launch::Async, [](int res){ ... })
|
future.then(QtFuture::Launch::Async, [](int res){ ... })
|
||||||
.then(QtFuture::Launch::Inherit, [](int res2){ ... });
|
.then(QtFuture::Launch::Inherit, [](int res2){ ... });
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
|
\sa onFailed()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \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)
|
||||||
@ -888,4 +892,83 @@
|
|||||||
computations if desired. When the asynchronous computation represented by this
|
computations if desired. When the asynchronous computation represented by this
|
||||||
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()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler)
|
||||||
|
|
||||||
|
\since 6.0
|
||||||
|
|
||||||
|
Attaches a failure handler to this future, to handle any exceptions that may
|
||||||
|
have been generated. Returns a QFuture of the parent type. The handler will
|
||||||
|
be invoked only in case of an exception, in the same thread as the parent
|
||||||
|
future has been running. \a handler is a callable which takes either no argument
|
||||||
|
or one argument, to filter by specific error types similar to
|
||||||
|
\l {https://en.cppreference.com/w/cpp/language/try_catch} {catch} statement.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
\code
|
||||||
|
QFuture<int> future = ...;
|
||||||
|
auto resultFuture = future.then([](int res) {
|
||||||
|
...
|
||||||
|
throw Error();
|
||||||
|
...
|
||||||
|
}).onFailed([](const Error &e) {
|
||||||
|
// Handle exceptions of type Error
|
||||||
|
...
|
||||||
|
return -1;
|
||||||
|
}).onFailed([] {
|
||||||
|
// Handle all other types of errors
|
||||||
|
...
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto result = resultFuture.result(); // result is -1
|
||||||
|
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
If there are multiple handlers attached, the first handler that matches with the
|
||||||
|
thrown exception type will be invoked. For example:
|
||||||
|
|
||||||
|
\code
|
||||||
|
QFuture<int> future = ...;
|
||||||
|
future.then([](int res) {
|
||||||
|
...
|
||||||
|
throw std::runtime_error("message");
|
||||||
|
...
|
||||||
|
}).onFailed([](const std::exception &e) {
|
||||||
|
// This handler will be invoked
|
||||||
|
}).onFailed([](const std::runtime_error &e) {
|
||||||
|
// This handler won't be invoked, because of the handler above.
|
||||||
|
});
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
If none of the handlers matches with the thrown exception type, the exception
|
||||||
|
will be propagated to the resulted future:
|
||||||
|
|
||||||
|
\code
|
||||||
|
QFuture<int> future = ...;
|
||||||
|
auto resultFuture = future.then([](int res) {
|
||||||
|
...
|
||||||
|
throw Error("message");
|
||||||
|
...
|
||||||
|
}).onFailed([](const std::exception &e) {
|
||||||
|
// Won't be invoked
|
||||||
|
}).onFailed([](const QException &e) {
|
||||||
|
// Won't be invoked
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto result = resultFuture.result();
|
||||||
|
} catch(...) {
|
||||||
|
// Handle the exception
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\note You can always attach a handler taking no argument, to handle all exception
|
||||||
|
types and avoid writing the try-catch block.
|
||||||
|
|
||||||
|
\sa then()
|
||||||
*/
|
*/
|
||||||
|
@ -109,6 +109,64 @@ struct ResultTypeHelper<
|
|||||||
using ResultType = std::invoke_result_t<std::decay_t<F>>;
|
using ResultType = std::invoke_result_t<std::decay_t<F>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helpers to resolve argument types of callables.
|
||||||
|
template<typename...>
|
||||||
|
struct ArgsType;
|
||||||
|
|
||||||
|
template<typename Arg, typename... Args>
|
||||||
|
struct ArgsType<Arg, Args...>
|
||||||
|
{
|
||||||
|
using First = Arg;
|
||||||
|
static const bool HasExtraArgs = (sizeof...(Args) > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct ArgsType<>
|
||||||
|
{
|
||||||
|
using First = void;
|
||||||
|
static const bool HasExtraArgs = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
struct ArgResolver : ArgResolver<decltype(&std::decay_t<F>::operator())>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename R, typename... Args>
|
||||||
|
struct ArgResolver<R(Args...)> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (*)(Args...)> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (&)(Args...)> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Class, typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (Class::*)(Args...)> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Class, typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (Class::*)(Args...) noexcept> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Class, typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (Class::*)(Args...) const> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Class, typename R, typename... Args>
|
||||||
|
struct ArgResolver<R (Class::*)(Args...) const noexcept> : public ArgsType<Args...>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
template<typename Function, typename ResultType, typename ParentResultType>
|
template<typename Function, typename ResultType, typename ParentResultType>
|
||||||
class Continuation
|
class Continuation
|
||||||
{
|
{
|
||||||
@ -186,6 +244,37 @@ private:
|
|||||||
QThreadPool *threadPool;
|
QThreadPool *threadPool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
class FailureHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void create(Function &&function, QFuture<ResultType> *future,
|
||||||
|
const QFutureInterface<ResultType> &promise);
|
||||||
|
|
||||||
|
FailureHandler(Function &&func, const QFuture<ResultType> &f,
|
||||||
|
const QFutureInterface<ResultType> &p)
|
||||||
|
: promise(p), parentFuture(f), handler(std::forward<Function>(func))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class ArgType>
|
||||||
|
void handleException();
|
||||||
|
void handleAllExceptions();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QFutureInterface<ResultType> promise;
|
||||||
|
const QFuture<ResultType> parentFuture;
|
||||||
|
Function handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
template<typename Function, typename ResultType, typename ParentResultType>
|
template<typename Function, typename ResultType, typename ParentResultType>
|
||||||
void Continuation<Function, ResultType, ParentResultType>::runFunction()
|
void Continuation<Function, ResultType, ParentResultType>::runFunction()
|
||||||
{
|
{
|
||||||
@ -297,7 +386,7 @@ void Continuation<Function, ResultType, ParentResultType>::create(Function &&fun
|
|||||||
|
|
||||||
p.setLaunchAsync(launchAsync);
|
p.setLaunchAsync(launchAsync);
|
||||||
|
|
||||||
auto continuation = [continuationJob, policy, launchAsync]() mutable {
|
auto continuation = [continuationJob, launchAsync]() mutable {
|
||||||
bool isLaunched = continuationJob->execute();
|
bool isLaunched = continuationJob->execute();
|
||||||
// If continuation is successfully launched, AsyncContinuation will be deleted
|
// If continuation is successfully launched, AsyncContinuation will be deleted
|
||||||
// by the QThreadPool which has started it. Synchronous continuation will be
|
// by the QThreadPool which has started it. Synchronous continuation will be
|
||||||
@ -337,6 +426,90 @@ void Continuation<Function, ResultType, ParentResultType>::create(Function &&fun
|
|||||||
f->d.setContinuation(continuation);
|
f->d.setContinuation(continuation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
void FailureHandler<Function, ResultType>::create(Function &&function, QFuture<ResultType> *future,
|
||||||
|
const QFutureInterface<ResultType> &promise)
|
||||||
|
{
|
||||||
|
Q_ASSERT(future);
|
||||||
|
|
||||||
|
FailureHandler<Function, ResultType> *failureHandler = new FailureHandler<Function, ResultType>(
|
||||||
|
std::forward<Function>(function), *future, promise);
|
||||||
|
|
||||||
|
auto failureContinuation = [failureHandler]() mutable {
|
||||||
|
failureHandler->run();
|
||||||
|
delete failureHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
future->d.setContinuation(std::move(failureContinuation));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
void FailureHandler<Function, ResultType>::run()
|
||||||
|
{
|
||||||
|
Q_ASSERT(parentFuture.isFinished());
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
|
||||||
|
if (parentFuture.d.exceptionStore().hasException()) {
|
||||||
|
using ArgType = typename QtPrivate::ArgResolver<Function>::First;
|
||||||
|
if constexpr (std::is_void_v<ArgType>) {
|
||||||
|
handleAllExceptions();
|
||||||
|
} else {
|
||||||
|
handleException<ArgType>();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr (!std::is_void_v<ResultType>)
|
||||||
|
promise.reportResult(parentFuture.result());
|
||||||
|
}
|
||||||
|
promise.reportFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
template<class ArgType>
|
||||||
|
void FailureHandler<Function, ResultType>::handleException()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
parentFuture.d.exceptionStore().throwPossibleException();
|
||||||
|
} catch (const ArgType &e) {
|
||||||
|
try {
|
||||||
|
// Handle exceptions matching with the handler's argument type
|
||||||
|
if constexpr (std::is_void_v<ResultType>) {
|
||||||
|
handler(e);
|
||||||
|
} else {
|
||||||
|
promise.reportResult(handler(e));
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
promise.reportException(std::current_exception());
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Exception doesn't match with handler's argument type, propagate
|
||||||
|
// the exception to be handled later.
|
||||||
|
promise.reportException(std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
void FailureHandler<Function, ResultType>::handleAllExceptions()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
parentFuture.d.exceptionStore().throwPossibleException();
|
||||||
|
} catch (...) {
|
||||||
|
try {
|
||||||
|
if constexpr (std::is_void_v<ResultType>) {
|
||||||
|
handler();
|
||||||
|
} else {
|
||||||
|
promise.reportResult(handler());
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
promise.reportException(std::current_exception());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
} // namespace QtPrivate
|
} // namespace QtPrivate
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -63,6 +63,11 @@ class QFutureWatcherBasePrivate;
|
|||||||
namespace QtPrivate {
|
namespace QtPrivate {
|
||||||
template<typename Function, typename ResultType, typename ParentResultType>
|
template<typename Function, typename ResultType, typename ParentResultType>
|
||||||
class Continuation;
|
class Continuation;
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
class FailureHandler;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
class Q_CORE_EXPORT QFutureInterfaceBase
|
class Q_CORE_EXPORT QFutureInterfaceBase
|
||||||
@ -158,6 +163,11 @@ private:
|
|||||||
template<typename Function, typename ResultType, typename ParentResultType>
|
template<typename Function, typename ResultType, typename ParentResultType>
|
||||||
friend class QtPrivate::Continuation;
|
friend class QtPrivate::Continuation;
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
template<class Function, class ResultType>
|
||||||
|
friend class QtPrivate::FailureHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setContinuation(std::function<void()> func);
|
void setContinuation(std::function<void()> func);
|
||||||
void runContinuation() const;
|
void runContinuation() const;
|
||||||
|
@ -104,6 +104,8 @@ private slots:
|
|||||||
#ifndef QT_NO_EXCEPTIONS
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
void thenOnExceptionFuture();
|
void thenOnExceptionFuture();
|
||||||
void thenThrows();
|
void thenThrows();
|
||||||
|
void onFailed();
|
||||||
|
void onFailedTestCallables();
|
||||||
#endif
|
#endif
|
||||||
void takeResults();
|
void takeResults();
|
||||||
void takeResult();
|
void takeResult();
|
||||||
@ -2149,6 +2151,337 @@ void tst_QFuture::thenThrows()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::onFailed()
|
||||||
|
{
|
||||||
|
// Ready exception void future
|
||||||
|
{
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto future = createExceptionFuture().then([&] { checkpoint = 1; }).onFailed([&] {
|
||||||
|
checkpoint = 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.waitForFinished();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 3;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::exception handler
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw std::exception();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](const std::exception &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 3;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 4;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 2);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then() throws an exception derived from QException
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw DerivedException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](const std::exception &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 3;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 4;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 1);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then() throws a custom exception
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw TestException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](const std::exception &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 3;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 4;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 3);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom exception handler
|
||||||
|
{
|
||||||
|
struct TestException
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw TestException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](const TestException &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 3;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 4;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 2);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all exceptions
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw QException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 1;
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 3;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 1);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler throws exception
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw QException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const QException &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
throw QException();
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&] {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 3;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 2);
|
||||||
|
QCOMPARE(res, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handler for exception
|
||||||
|
{
|
||||||
|
QFutureInterface<int> promise;
|
||||||
|
|
||||||
|
int checkpoint = 0;
|
||||||
|
auto then = promise.future()
|
||||||
|
.then([&](int res) {
|
||||||
|
throw QException();
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then([&](int res) { return res + 1; })
|
||||||
|
.onFailed([&](const std::exception &) {
|
||||||
|
checkpoint = 1;
|
||||||
|
throw std::exception();
|
||||||
|
return -1;
|
||||||
|
})
|
||||||
|
.onFailed([&](QException &) {
|
||||||
|
checkpoint = 2;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.reportStarted();
|
||||||
|
promise.reportResult(1);
|
||||||
|
promise.reportFinished();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = then.result();
|
||||||
|
} catch (...) {
|
||||||
|
checkpoint = 3;
|
||||||
|
}
|
||||||
|
QCOMPARE(checkpoint, 3);
|
||||||
|
QCOMPARE(res, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Callable>
|
||||||
|
bool runForCallable(Callable &&handler)
|
||||||
|
{
|
||||||
|
QFuture<int> future = createExceptionResultFuture()
|
||||||
|
.then([&](int) { return 1; })
|
||||||
|
.onFailed(std::forward<Callable>(handler));
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
res = future.result();
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return res == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int foo()
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QFuture::onFailedTestCallables()
|
||||||
|
{
|
||||||
|
QVERIFY(runForCallable([&] { return -1; }));
|
||||||
|
QVERIFY(runForCallable(foo));
|
||||||
|
QVERIFY(runForCallable(&foo));
|
||||||
|
|
||||||
|
std::function<int()> func = foo;
|
||||||
|
QVERIFY(runForCallable(func));
|
||||||
|
|
||||||
|
struct Functor1
|
||||||
|
{
|
||||||
|
int operator()() { return -1; }
|
||||||
|
static int foo() { return -1; }
|
||||||
|
};
|
||||||
|
QVERIFY(runForCallable(Functor1()));
|
||||||
|
QVERIFY(runForCallable(Functor1::foo));
|
||||||
|
|
||||||
|
struct Functor2
|
||||||
|
{
|
||||||
|
int operator()() const { return -1; }
|
||||||
|
static int foo() { return -1; }
|
||||||
|
};
|
||||||
|
QVERIFY(runForCallable(Functor2()));
|
||||||
|
|
||||||
|
struct Functor3
|
||||||
|
{
|
||||||
|
int operator()() const noexcept { return -1; }
|
||||||
|
static int foo() { return -1; }
|
||||||
|
};
|
||||||
|
QVERIFY(runForCallable(Functor3()));
|
||||||
|
}
|
||||||
|
|
||||||
#endif // QT_NO_EXCEPTIONS
|
#endif // QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
void tst_QFuture::testSingleResult(const UniquePtr &p)
|
void tst_QFuture::testSingleResult(const UniquePtr &p)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user