Add support of invoking QFuture continuations in a given context
Added overloads of .then()/.onFailed()/.onCanceled() which take a pointer of a context object, and invoke the continuations in the object's thread. Task-number: QTBUG-86794 Change-Id: I0f3cbb0500695673fc4087af5d4b96b416e3e1ce Reviewed-by: Andrei Golubev <andrei.golubev@qt.io> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io> Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
parent
b283ce1e83
commit
335acffe1d
@ -252,3 +252,34 @@ auto resultFuture = testFuture.then([](int res) {
|
||||
// Block 6
|
||||
});
|
||||
//! [16]
|
||||
|
||||
//! [17]
|
||||
// somewhere in the main thread
|
||||
auto future = QtConcurrent::run([] {
|
||||
// This will run in a separate thread
|
||||
...
|
||||
}).then(this, [] {
|
||||
// Update UI elements
|
||||
});
|
||||
//! [17]
|
||||
|
||||
//! [18]
|
||||
auto future = QtConcurrent::run([] {
|
||||
...
|
||||
}).then(this, [] {
|
||||
// Update UI elements
|
||||
}).then([] {
|
||||
// This will also run in the main thread
|
||||
});
|
||||
//! [18]
|
||||
|
||||
//! [19]
|
||||
// somewhere in the main thread
|
||||
auto future = QtConcurrent::run([] {
|
||||
// This will run in a separate thread
|
||||
...
|
||||
throw std::exception();
|
||||
}).onFailed(this, [] {
|
||||
// Update UI elements
|
||||
});
|
||||
//! [19]
|
||||
|
@ -173,15 +173,25 @@ QT_WARNING_POP
|
||||
template<class Function>
|
||||
QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function);
|
||||
|
||||
template<class Function>
|
||||
QFuture<ResultType<Function>> then(QObject *context, Function &&function);
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
template<class Function,
|
||||
typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>>
|
||||
QFuture<T> onFailed(Function &&handler);
|
||||
|
||||
template<class Function,
|
||||
typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>>
|
||||
QFuture<T> onFailed(QObject *context, Function &&handler);
|
||||
#endif
|
||||
|
||||
template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>>
|
||||
QFuture<T> onCanceled(Function &&handler);
|
||||
|
||||
template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>>
|
||||
QFuture<T> onCanceled(QObject *context, Function &&handler);
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
@ -363,8 +373,18 @@ QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QTh
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
template<class T>
|
||||
template<class Function>
|
||||
QFuture<typename QFuture<T>::template ResultType<Function>> QFuture<T>::then(QObject *context,
|
||||
Function &&function)
|
||||
{
|
||||
QFutureInterface<ResultType<Function>> promise(QFutureInterfaceBase::State::Pending);
|
||||
QtPrivate::Continuation<std::decay_t<Function>, ResultType<Function>, T>::create(
|
||||
std::forward<Function>(function), this, promise, context);
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
template<class T>
|
||||
template<class Function, typename>
|
||||
QFuture<T> QFuture<T>::onFailed(Function &&handler)
|
||||
@ -375,6 +395,16 @@ QFuture<T> QFuture<T>::onFailed(Function &&handler)
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class Function, typename>
|
||||
QFuture<T> QFuture<T>::onFailed(QObject *context, Function &&handler)
|
||||
{
|
||||
QFutureInterface<T> promise(QFutureInterfaceBase::State::Pending);
|
||||
QtPrivate::FailureHandler<std::decay_t<Function>, T>::create(std::forward<Function>(handler),
|
||||
this, promise, context);
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
@ -387,6 +417,16 @@ QFuture<T> QFuture<T>::onCanceled(Function &&handler)
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class Function, typename>
|
||||
QFuture<T> QFuture<T>::onCanceled(QObject *context, Function &&handler)
|
||||
{
|
||||
QFutureInterface<T> promise(QFutureInterfaceBase::State::Pending);
|
||||
QtPrivate::CanceledHandler<std::decay_t<Function>, T>::create(std::forward<Function>(handler),
|
||||
this, promise, context);
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
inline QFuture<void> QFutureInterface<void>::future()
|
||||
{
|
||||
return QFuture<void>(this);
|
||||
|
@ -1149,6 +1149,36 @@
|
||||
\sa onFailed(), onCanceled()
|
||||
*/
|
||||
|
||||
/*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(QObject *context, Function &&function)
|
||||
|
||||
\since 6.1
|
||||
\overload
|
||||
|
||||
Attaches a continuation to this future, allowing to chain multiple asynchronous
|
||||
computations if desired. When the asynchronous computation represented by this
|
||||
future finishes, \a function will be invoked in the thread of the \a context object.
|
||||
This can be useful if the continuation needs to be invoked in a specific thread.
|
||||
For example:
|
||||
|
||||
\snippet code/src_corelib_thread_qfuture.cpp 17
|
||||
|
||||
The continuation attached into QtConcurrent::run updates the UI elements and cannot
|
||||
be invoked from a non-gui thread. So \c this is provided as a context to \c .then(),
|
||||
to make sure that it will be invoked in the main thread.
|
||||
|
||||
The following continuations will be also invoked from the same context,
|
||||
unless a different context or launch policy is specified:
|
||||
|
||||
\snippet code/src_corelib_thread_qfuture.cpp 18
|
||||
|
||||
This is because by default \c .then() is invoked from the same thread as the parent.
|
||||
|
||||
\note When calling this method, it should be guaranteed that the \a context stays alive
|
||||
throughout the execution of the chain.
|
||||
|
||||
\sa onFailed(), onCanceled()
|
||||
*/
|
||||
|
||||
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler)
|
||||
|
||||
\since 6.0
|
||||
@ -1182,6 +1212,29 @@
|
||||
\sa then(), onCanceled()
|
||||
*/
|
||||
|
||||
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(QObject *context, Function &&handler)
|
||||
|
||||
\since 6.1
|
||||
\overload
|
||||
|
||||
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 thread of the \a context object.
|
||||
This can be useful if the failure needs to be handled in a specific thread.
|
||||
For example:
|
||||
|
||||
\snippet code/src_corelib_thread_qfuture.cpp 19
|
||||
|
||||
The failure handler attached into QtConcurrent::run updates the UI elements and cannot
|
||||
be invoked from a non-gui thread. So \c this is provided as a context to \c .onFailed(),
|
||||
to make sure that it will be invoked in the main thread.
|
||||
|
||||
\note When calling this method, it should be guaranteed that the \a context stays alive
|
||||
throughout the execution of the chain.
|
||||
|
||||
\sa then(), onCanceled()
|
||||
*/
|
||||
|
||||
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onCanceled(Function &&handler)
|
||||
|
||||
\since 6.0
|
||||
@ -1194,3 +1247,19 @@
|
||||
|
||||
\sa then(), onFailed()
|
||||
*/
|
||||
|
||||
/*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onCanceled(QObject *context, Function &&handler)
|
||||
|
||||
\since 6.1
|
||||
\overload
|
||||
|
||||
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 thread of the \a context object. This can be useful if the cancellation
|
||||
needs to be handled in a specific thread.
|
||||
|
||||
\note When calling this method, it should be guaranteed that the \a context stays alive
|
||||
throughout the execution of the chain.
|
||||
|
||||
\sa then(), onFailed()
|
||||
*/
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include <QtCore/qfutureinterface.h>
|
||||
#include <QtCore/qthreadpool.h>
|
||||
#include <QtCore/qexception.h>
|
||||
#include <QtCore/qpointer.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -265,6 +266,10 @@ public:
|
||||
static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &p,
|
||||
QThreadPool *pool);
|
||||
|
||||
template<typename F = Function>
|
||||
static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &p,
|
||||
QObject *context);
|
||||
|
||||
private:
|
||||
void fulfillPromiseWithResult();
|
||||
void fulfillVoidPromise();
|
||||
@ -343,6 +348,10 @@ public:
|
||||
static void create(F &&function, QFuture<ResultType> *future,
|
||||
const QFutureInterface<ResultType> &promise);
|
||||
|
||||
template<typename F = Function>
|
||||
static void create(F &&function, QFuture<ResultType> *future,
|
||||
QFutureInterface<ResultType> &promise, QObject *context);
|
||||
|
||||
template<typename F = Function>
|
||||
FailureHandler(F &&func, const QFuture<ResultType> &f, const QFutureInterface<ResultType> &p)
|
||||
: promise(p), parentFuture(f), handler(std::forward<F>(func))
|
||||
@ -518,7 +527,30 @@ void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
|
||||
}
|
||||
};
|
||||
|
||||
f->d.setContinuation(continuation);
|
||||
f->d.setContinuation(std::move(continuation));
|
||||
}
|
||||
|
||||
template<typename Function, typename ResultType, typename ParentResultType>
|
||||
template<typename F>
|
||||
void Continuation<Function, ResultType, ParentResultType>::create(F &&func,
|
||||
QFuture<ParentResultType> *f,
|
||||
QFutureInterface<ResultType> &p,
|
||||
QObject *context)
|
||||
{
|
||||
Q_ASSERT(f);
|
||||
|
||||
auto continuation = [func = std::forward<F>(func), p, context = QPointer<QObject>(context)](
|
||||
const QFutureInterfaceBase &parentData) mutable {
|
||||
Q_ASSERT(context);
|
||||
const auto parent = QFutureInterface<ParentResultType>(parentData).future();
|
||||
QMetaObject::invokeMethod(context, [func = std::forward<F>(func), p, parent]() mutable {
|
||||
SyncContinuation<Function, ResultType, ParentResultType> continuationJob(
|
||||
std::forward<Function>(func), parent, p);
|
||||
continuationJob.execute();
|
||||
});
|
||||
};
|
||||
|
||||
f->d.setContinuation(std::move(continuation));
|
||||
}
|
||||
|
||||
template<typename Function, typename ResultType, typename ParentResultType>
|
||||
@ -600,6 +632,30 @@ void FailureHandler<Function, ResultType>::create(F &&function, QFuture<ResultTy
|
||||
future->d.setContinuation(std::move(failureContinuation));
|
||||
}
|
||||
|
||||
template<class Function, class ResultType>
|
||||
template<class F>
|
||||
void FailureHandler<Function, ResultType>::create(F &&function, QFuture<ResultType> *future,
|
||||
QFutureInterface<ResultType> &promise,
|
||||
QObject *context)
|
||||
{
|
||||
Q_ASSERT(future);
|
||||
|
||||
auto failureContinuation =
|
||||
[function = std::forward<F>(function), promise,
|
||||
context = QPointer<QObject>(context)](const QFutureInterfaceBase &parentData) mutable {
|
||||
Q_ASSERT(context);
|
||||
const auto parent = QFutureInterface<ResultType>(parentData).future();
|
||||
QMetaObject::invokeMethod(
|
||||
context, [function = std::forward<F>(function), promise, parent]() mutable {
|
||||
FailureHandler<Function, ResultType> failureHandler(
|
||||
std::forward<Function>(function), parent, promise);
|
||||
failureHandler.run();
|
||||
});
|
||||
};
|
||||
|
||||
future->d.setContinuation(std::move(failureContinuation));
|
||||
}
|
||||
|
||||
template<class Function, class ResultType>
|
||||
void FailureHandler<Function, ResultType>::run()
|
||||
{
|
||||
@ -676,33 +732,59 @@ public:
|
||||
auto canceledContinuation = [promise, handler = std::forward<F>(handler)](
|
||||
const QFutureInterfaceBase &parentData) mutable {
|
||||
auto parentFuture = QFutureInterface<ResultType>(parentData).future();
|
||||
|
||||
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();
|
||||
run(std::forward<F>(handler), parentFuture, promise);
|
||||
};
|
||||
future->d.setContinuation(std::move(canceledContinuation));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
template<class F = Function>
|
||||
static QFuture<ResultType> create(F &&handler, QFuture<ResultType> *future,
|
||||
QFutureInterface<ResultType> &promise, QObject *context)
|
||||
{
|
||||
Q_ASSERT(future);
|
||||
|
||||
auto canceledContinuation = [promise, handler = std::forward<F>(handler),
|
||||
context = QPointer<QObject>(context)](
|
||||
const QFutureInterfaceBase &parentData) mutable {
|
||||
Q_ASSERT(context);
|
||||
auto parentFuture = QFutureInterface<ResultType>(parentData).future();
|
||||
QMetaObject::invokeMethod(
|
||||
context, [promise, parentFuture, handler = std::forward<F>(handler)]() mutable {
|
||||
run(std::forward<F>(handler), parentFuture, promise);
|
||||
});
|
||||
};
|
||||
future->d.setContinuation(std::move(canceledContinuation));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
template<class F = Function>
|
||||
static void run(F &&handler, QFuture<ResultType> &parentFuture,
|
||||
QFutureInterface<ResultType> &promise)
|
||||
{
|
||||
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<F>(handler));
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
} catch (...) {
|
||||
promise.reportException(std::current_exception());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
QtPrivate::fulfillPromise(promise, parentFuture);
|
||||
}
|
||||
|
||||
promise.reportFinished();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QtPrivate
|
||||
|
@ -138,6 +138,7 @@ private slots:
|
||||
void onFailedForMoveOnlyTypes();
|
||||
#endif
|
||||
void onCanceled();
|
||||
void continuationsWithContext();
|
||||
#if 0
|
||||
// TODO: enable when QFuture::takeResults() is enabled
|
||||
void takeResults();
|
||||
@ -2820,6 +2821,85 @@ void tst_QFuture::onCanceled()
|
||||
#endif // QT_NO_EXCEPTIONS
|
||||
}
|
||||
|
||||
void tst_QFuture::continuationsWithContext()
|
||||
{
|
||||
QThread thread;
|
||||
thread.start();
|
||||
|
||||
auto context = new QObject();
|
||||
context->moveToThread(&thread);
|
||||
|
||||
auto tstThread = QThread::currentThread();
|
||||
|
||||
// .then()
|
||||
{
|
||||
auto future = QtFuture::makeReadyFuture(0)
|
||||
.then([&](int val) {
|
||||
if (QThread::currentThread() != tstThread)
|
||||
return 0;
|
||||
return val + 1;
|
||||
})
|
||||
.then(context,
|
||||
[&](int val) {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return val + 1;
|
||||
})
|
||||
.then([&](int val) {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return val + 1;
|
||||
});
|
||||
QCOMPARE(future.result(), 3);
|
||||
}
|
||||
|
||||
// .onCanceled
|
||||
{
|
||||
auto future = createCanceledFuture<int>()
|
||||
.onCanceled(context,
|
||||
[&] {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return 1;
|
||||
})
|
||||
.then([&](int val) {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return val + 1;
|
||||
});
|
||||
QCOMPARE(future.result(), 2);
|
||||
}
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
// .onFaled()
|
||||
{
|
||||
auto future = QtFuture::makeReadyFuture()
|
||||
.then([&] {
|
||||
if (QThread::currentThread() != tstThread)
|
||||
return 0;
|
||||
throw std::runtime_error("error");
|
||||
})
|
||||
.onFailed(context,
|
||||
[&] {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return 1;
|
||||
})
|
||||
.then([&](int val) {
|
||||
if (QThread::currentThread() != &thread)
|
||||
return 0;
|
||||
return val + 1;
|
||||
});
|
||||
QCOMPARE(future.result(), 2);
|
||||
}
|
||||
#endif // QT_NO_EXCEPTIONS
|
||||
|
||||
context->deleteLater();
|
||||
|
||||
thread.quit();
|
||||
thread.wait();
|
||||
}
|
||||
|
||||
void tst_QFuture::testSingleResult(const UniquePtr &p)
|
||||
{
|
||||
QVERIFY(p.get() != nullptr);
|
||||
|
Loading…
x
Reference in New Issue
Block a user