diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp index da17b390fd4..a5e1a7f6e4a 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -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] diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h index 101bdc78086..a56e9ee3ff7 100644 --- a/src/corelib/thread/qfuture.h +++ b/src/corelib/thread/qfuture.h @@ -173,15 +173,25 @@ QT_WARNING_POP template QFuture> then(QThreadPool *pool, Function &&function); + template + QFuture> then(QObject *context, Function &&function); + #ifndef QT_NO_EXCEPTIONS template::HasExtraArgs>> QFuture onFailed(Function &&handler); + + template::HasExtraArgs>> + QFuture onFailed(QObject *context, Function &&handler); #endif template>> QFuture onCanceled(Function &&handler); + template>> + QFuture onCanceled(QObject *context, Function &&handler); + class const_iterator { public: @@ -363,8 +373,18 @@ QFuture::template ResultType> QFuture::then(QTh return promise.future(); } -#ifndef QT_NO_EXCEPTIONS +template +template +QFuture::template ResultType> QFuture::then(QObject *context, + Function &&function) +{ + QFutureInterface> promise(QFutureInterfaceBase::State::Pending); + QtPrivate::Continuation, ResultType, T>::create( + std::forward(function), this, promise, context); + return promise.future(); +} +#ifndef QT_NO_EXCEPTIONS template template QFuture QFuture::onFailed(Function &&handler) @@ -375,6 +395,16 @@ QFuture QFuture::onFailed(Function &&handler) return promise.future(); } +template +template +QFuture QFuture::onFailed(QObject *context, Function &&handler) +{ + QFutureInterface promise(QFutureInterfaceBase::State::Pending); + QtPrivate::FailureHandler, T>::create(std::forward(handler), + this, promise, context); + return promise.future(); +} + #endif template @@ -387,6 +417,16 @@ QFuture QFuture::onCanceled(Function &&handler) return promise.future(); } +template +template +QFuture QFuture::onCanceled(QObject *context, Function &&handler) +{ + QFutureInterface promise(QFutureInterfaceBase::State::Pending); + QtPrivate::CanceledHandler, T>::create(std::forward(handler), + this, promise, context); + return promise.future(); +} + inline QFuture QFutureInterface::future() { return QFuture(this); diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index 7603fe22306..0b7e59d9e14 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -1149,6 +1149,36 @@ \sa onFailed(), onCanceled() */ +/*! \fn template template QFuture::ResultType> QFuture::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 template QFuture QFuture::onFailed(Function &&handler) \since 6.0 @@ -1182,6 +1212,29 @@ \sa then(), onCanceled() */ +/*! \fn template template QFuture QFuture::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 template QFuture QFuture::onCanceled(Function &&handler) \since 6.0 @@ -1194,3 +1247,19 @@ \sa then(), onFailed() */ + +/*! \fn template template QFuture QFuture::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() +*/ diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 3f95d61b99c..8e7ba0c4b59 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -50,6 +50,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -265,6 +266,10 @@ public: static void create(F &&func, QFuture *f, QFutureInterface &p, QThreadPool *pool); + template + static void create(F &&func, QFuture *f, QFutureInterface &p, + QObject *context); + private: void fulfillPromiseWithResult(); void fulfillVoidPromise(); @@ -343,6 +348,10 @@ public: static void create(F &&function, QFuture *future, const QFutureInterface &promise); + template + static void create(F &&function, QFuture *future, + QFutureInterface &promise, QObject *context); + template FailureHandler(F &&func, const QFuture &f, const QFutureInterface &p) : promise(p), parentFuture(f), handler(std::forward(func)) @@ -518,7 +527,30 @@ void Continuation::create(F &&func, } }; - f->d.setContinuation(continuation); + f->d.setContinuation(std::move(continuation)); +} + +template +template +void Continuation::create(F &&func, + QFuture *f, + QFutureInterface &p, + QObject *context) +{ + Q_ASSERT(f); + + auto continuation = [func = std::forward(func), p, context = QPointer(context)]( + const QFutureInterfaceBase &parentData) mutable { + Q_ASSERT(context); + const auto parent = QFutureInterface(parentData).future(); + QMetaObject::invokeMethod(context, [func = std::forward(func), p, parent]() mutable { + SyncContinuation continuationJob( + std::forward(func), parent, p); + continuationJob.execute(); + }); + }; + + f->d.setContinuation(std::move(continuation)); } template @@ -600,6 +632,30 @@ void FailureHandler::create(F &&function, QFutured.setContinuation(std::move(failureContinuation)); } +template +template +void FailureHandler::create(F &&function, QFuture *future, + QFutureInterface &promise, + QObject *context) +{ + Q_ASSERT(future); + + auto failureContinuation = + [function = std::forward(function), promise, + context = QPointer(context)](const QFutureInterfaceBase &parentData) mutable { + Q_ASSERT(context); + const auto parent = QFutureInterface(parentData).future(); + QMetaObject::invokeMethod( + context, [function = std::forward(function), promise, parent]() mutable { + FailureHandler failureHandler( + std::forward(function), parent, promise); + failureHandler.run(); + }); + }; + + future->d.setContinuation(std::move(failureContinuation)); +} + template void FailureHandler::run() { @@ -676,33 +732,59 @@ public: auto canceledContinuation = [promise, handler = std::forward(handler)]( const QFutureInterfaceBase &parentData) mutable { auto parentFuture = QFutureInterface(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(handler)); -#ifndef QT_NO_EXCEPTIONS - } catch (...) { - promise.reportException(std::current_exception()); - } - } -#endif - } else { - QtPrivate::fulfillPromise(promise, parentFuture); - } - - promise.reportFinished(); + run(std::forward(handler), parentFuture, promise); }; future->d.setContinuation(std::move(canceledContinuation)); return promise.future(); } + + template + static QFuture create(F &&handler, QFuture *future, + QFutureInterface &promise, QObject *context) + { + Q_ASSERT(future); + + auto canceledContinuation = [promise, handler = std::forward(handler), + context = QPointer(context)]( + const QFutureInterfaceBase &parentData) mutable { + Q_ASSERT(context); + auto parentFuture = QFutureInterface(parentData).future(); + QMetaObject::invokeMethod( + context, [promise, parentFuture, handler = std::forward(handler)]() mutable { + run(std::forward(handler), parentFuture, promise); + }); + }; + future->d.setContinuation(std::move(canceledContinuation)); + return promise.future(); + } + + template + static void run(F &&handler, QFuture &parentFuture, + QFutureInterface &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(handler)); +#ifndef QT_NO_EXCEPTIONS + } catch (...) { + promise.reportException(std::current_exception()); + } + } +#endif + } else { + QtPrivate::fulfillPromise(promise, parentFuture); + } + + promise.reportFinished(); + } }; } // namespace QtPrivate diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index c7d552cb30c..3616500af80 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -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() + .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);