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:
Sona Kurazyan 2020-12-04 17:20:29 +01:00
parent b283ce1e83
commit 335acffe1d
5 changed files with 327 additions and 25 deletions

View File

@ -252,3 +252,34 @@ auto resultFuture = testFuture.then([](int res) {
// Block 6 // Block 6
}); });
//! [16] //! [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]

View File

@ -173,15 +173,25 @@ QT_WARNING_POP
template<class Function> template<class Function>
QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function); QFuture<ResultType<Function>> then(QThreadPool *pool, Function &&function);
template<class Function>
QFuture<ResultType<Function>> then(QObject *context, Function &&function);
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
template<class Function, template<class Function,
typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>> typename = std::enable_if_t<!QtPrivate::ArgResolver<Function>::HasExtraArgs>>
QFuture<T> onFailed(Function &&handler); 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 #endif
template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>> template<class Function, typename = std::enable_if_t<std::is_invocable_r_v<T, Function>>>
QFuture<T> onCanceled(Function &&handler); 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 class const_iterator
{ {
public: public:
@ -363,8 +373,18 @@ 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>
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 T>
template<class Function, typename> template<class Function, typename>
QFuture<T> QFuture<T>::onFailed(Function &&handler) QFuture<T> QFuture<T>::onFailed(Function &&handler)
@ -375,6 +395,16 @@ QFuture<T> QFuture<T>::onFailed(Function &&handler)
return promise.future(); 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 #endif
template<class T> template<class T>
@ -387,6 +417,16 @@ QFuture<T> QFuture<T>::onCanceled(Function &&handler)
return promise.future(); 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() inline QFuture<void> QFutureInterface<void>::future()
{ {
return QFuture<void>(this); return QFuture<void>(this);

View File

@ -1149,6 +1149,36 @@
\sa onFailed(), onCanceled() \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) /*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onFailed(Function &&handler)
\since 6.0 \since 6.0
@ -1182,6 +1212,29 @@
\sa then(), onCanceled() \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) /*! \fn template<class T> template<class Function> QFuture<T> QFuture<T>::onCanceled(Function &&handler)
\since 6.0 \since 6.0
@ -1194,3 +1247,19 @@
\sa then(), onFailed() \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()
*/

View File

@ -50,6 +50,7 @@
#include <QtCore/qfutureinterface.h> #include <QtCore/qfutureinterface.h>
#include <QtCore/qthreadpool.h> #include <QtCore/qthreadpool.h>
#include <QtCore/qexception.h> #include <QtCore/qexception.h>
#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -265,6 +266,10 @@ public:
static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &p, static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &p,
QThreadPool *pool); QThreadPool *pool);
template<typename F = Function>
static void create(F &&func, QFuture<ParentResultType> *f, QFutureInterface<ResultType> &p,
QObject *context);
private: private:
void fulfillPromiseWithResult(); void fulfillPromiseWithResult();
void fulfillVoidPromise(); void fulfillVoidPromise();
@ -343,6 +348,10 @@ public:
static void create(F &&function, QFuture<ResultType> *future, static void create(F &&function, QFuture<ResultType> *future,
const QFutureInterface<ResultType> &promise); 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> template<typename F = Function>
FailureHandler(F &&func, const QFuture<ResultType> &f, const QFutureInterface<ResultType> &p) FailureHandler(F &&func, const QFuture<ResultType> &f, const QFutureInterface<ResultType> &p)
: promise(p), parentFuture(f), handler(std::forward<F>(func)) : 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> 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)); 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> template<class Function, class ResultType>
void FailureHandler<Function, ResultType>::run() void FailureHandler<Function, ResultType>::run()
{ {
@ -676,33 +732,59 @@ public:
auto canceledContinuation = [promise, handler = std::forward<F>(handler)]( auto canceledContinuation = [promise, handler = std::forward<F>(handler)](
const QFutureInterfaceBase &parentData) mutable { const QFutureInterfaceBase &parentData) mutable {
auto parentFuture = QFutureInterface<ResultType>(parentData).future(); auto parentFuture = QFutureInterface<ResultType>(parentData).future();
run(std::forward<F>(handler), parentFuture, 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<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)); future->d.setContinuation(std::move(canceledContinuation));
return promise.future(); 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 } // namespace QtPrivate

View File

@ -138,6 +138,7 @@ private slots:
void onFailedForMoveOnlyTypes(); void onFailedForMoveOnlyTypes();
#endif #endif
void onCanceled(); void onCanceled();
void continuationsWithContext();
#if 0 #if 0
// TODO: enable when QFuture::takeResults() is enabled // TODO: enable when QFuture::takeResults() is enabled
void takeResults(); void takeResults();
@ -2820,6 +2821,85 @@ void tst_QFuture::onCanceled()
#endif // QT_NO_EXCEPTIONS #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) void tst_QFuture::testSingleResult(const UniquePtr &p)
{ {
QVERIFY(p.get() != nullptr); QVERIFY(p.get() != nullptr);