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 0580f142d7..da17b390fd 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -220,3 +220,35 @@ QtFuture::connect(&object, &Object::singleArgSignal).then([](int value) { // handle other exceptions }); //! [14] + +//! [15] +QFuture 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 testFuture = ...; +auto resultFuture = testFuture.then([](int res) { + // Block 1 +}).onFailed([] { + // Block 3 +}).then([] { + // Block 4 +}).onFailed([] { + // Block 5 +}).onCanceled([] { + // Block 6 +}); +//! [16] diff --git a/src/corelib/thread/qfuture.h b/src/corelib/thread/qfuture.h index bfca16b641..22ab0c13de 100644 --- a/src/corelib/thread/qfuture.h +++ b/src/corelib/thread/qfuture.h @@ -168,6 +168,9 @@ public: QFuture onFailed(Function &&handler); #endif + template>> + QFuture onCanceled(Function &&handler); + class const_iterator { public: @@ -281,6 +284,9 @@ private: template friend class QtPrivate::Continuation; + template + friend class QtPrivate::CanceledHandler; + #ifndef QT_NO_EXCEPTIONS template friend class QtPrivate::FailureHandler; @@ -359,6 +365,15 @@ QFuture QFuture::onFailed(Function &&handler) #endif +template +template +QFuture QFuture::onCanceled(Function &&handler) +{ + QFutureInterface promise(QFutureInterfaceBase::State::Pending); + QtPrivate::CanceledHandler::create(std::forward(handler), this, promise); + 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 f977a48f96..1846aa377d 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -55,8 +55,9 @@ results in the future. If the result of one asynchronous computation needs to be passed - to another, QFuture provides a convenient way of chaining multiple - sequential computations using then(). Additionally, onFailed() can be used + to another, QFuture provides a convenient way of chaining multiple sequential + 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 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 @@ -76,6 +77,31 @@ \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 instance, the computation can be canceled with the cancel() function. To 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 also be canceled. - \sa onFailed() + \sa onFailed(), onCanceled() */ /*! \fn template template QFuture::ResultType> QFuture::then(QtFuture::Launch policy, Function &&function) @@ -926,7 +952,7 @@ .then(QtFuture::Launch::Inherit, [](int res2){ ... }); \endcode - \sa onFailed() + \sa onFailed(), onCanceled() */ /*! \fn template template QFuture::ResultType> QFuture::then(QThreadPool *pool, Function &&function) @@ -939,7 +965,7 @@ future finishes, \a function will be invoked in a separate thread taken from the QThreadPool \a pool. - \sa onFailed() + \sa onFailed(), onCanceled() */ /*! \fn template template QFuture QFuture::onFailed(Function &&handler) @@ -970,5 +996,16 @@ \note You can always attach a handler taking no argument, to handle all exception types and avoid writing the try-catch block. - \sa then() + \sa then(), onCanceled() +*/ + +/*! \fn template template QFuture QFuture::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() */ diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 0e0cef0dbf..82c89cfa38 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -495,6 +495,28 @@ void Continuation::fulfillPromise(Args & promise.reportAndMoveResult(std::invoke(function, std::forward(args)...)); } +template +void fulfillPromise(QFutureInterface &promise, QFuture &future) +{ + if constexpr (!std::is_void_v) { + if constexpr (std::is_copy_constructible_v) + promise.reportResult(future.result()); + else + promise.reportAndMoveResult(future.takeResult()); + } +} + +template +void fulfillPromise(QFutureInterface &promise, Function &&handler) +{ + if constexpr (std::is_void_v) + handler(); + else if constexpr (std::is_copy_constructible_v) + promise.reportResult(handler()); + else + promise.reportAndMoveResult(handler()); +} + #ifndef QT_NO_EXCEPTIONS template @@ -529,12 +551,7 @@ void FailureHandler::run() handleException(); } } else { - if constexpr (!std::is_void_v) { - if constexpr (std::is_copy_constructible_v) - promise.reportResult(parentFuture.result()); - else - promise.reportAndMoveResult(parentFuture.takeResult()); - } + QtPrivate::fulfillPromise(promise, parentFuture); } promise.reportFinished(); } @@ -573,12 +590,7 @@ void FailureHandler::handleAllExceptions() parentFuture.d.exceptionStore().throwPossibleException(); } catch (...) { try { - if constexpr (std::is_void_v) - handler(); - else if constexpr (std::is_copy_constructible_v) - promise.reportResult(handler()); - else - promise.reportAndMoveResult(handler()); + QtPrivate::fulfillPromise(promise, std::forward(handler)); } catch (...) { promise.reportException(std::current_exception()); } @@ -587,6 +599,45 @@ void FailureHandler::handleAllExceptions() #endif // QT_NO_EXCEPTIONS +template +class CanceledHandler +{ +public: + static QFuture create(Function &&handler, QFuture *future, + QFutureInterface 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(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 QtFuture { diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h index 02f854e0de..2f9b455d8d 100644 --- a/src/corelib/thread/qfutureinterface.h +++ b/src/corelib/thread/qfutureinterface.h @@ -64,6 +64,9 @@ namespace QtPrivate { template class Continuation; +template +class CanceledHandler; + #ifndef QT_NO_EXCEPTIONS template class FailureHandler; @@ -163,6 +166,9 @@ private: template friend class QtPrivate::Continuation; + template + friend class QtPrivate::CanceledHandler; + #ifndef QT_NO_EXCEPTIONS template friend class QtPrivate::FailureHandler; diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index f61aa1361e..a7f308de59 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -131,6 +131,7 @@ private slots: void onFailedTestCallables(); void onFailedForMoveOnlyTypes(); #endif + void onCanceled(); void takeResults(); void takeResult(); void runAndTake(); @@ -1933,18 +1934,24 @@ void tst_QFuture::thenForMoveOnlyTypes() QVERIFY(runThenForMoveOnly([] { return std::make_unique(42); })); } +template +QFuture createCanceledFuture() +{ + QFutureInterface promise; + promise.reportStarted(); + promise.reportCanceled(); + promise.reportFinished(); + return promise.future(); +} + void tst_QFuture::thenOnCanceledFuture() { // Continuations on a canceled future { - QFutureInterface promise; - promise.reportStarted(); - promise.reportCanceled(); - promise.reportFinished(); - int thenResult = 0; - QFuture then = - promise.future().then([&]() { ++thenResult; }).then([&]() { ++thenResult; }); + QFuture then = createCanceledFuture().then([&]() { ++thenResult; }).then([&]() { + ++thenResult; + }); QVERIFY(then.isCanceled()); QCOMPARE(thenResult, 0); @@ -1970,16 +1977,10 @@ void tst_QFuture::thenOnCanceledFuture() // Continuations on a canceled future { - QFutureInterface promise; - promise.reportStarted(); - promise.reportCanceled(); - promise.reportFinished(); - int thenResult = 0; - QFuture then = - promise.future().then(QtFuture::Launch::Async, [&]() { ++thenResult; }).then([&]() { - ++thenResult; - }); + QFuture then = createCanceledFuture() + .then(QtFuture::Launch::Async, [&]() { ++thenResult; }) + .then([&]() { ++thenResult; }); QVERIFY(then.isCanceled()); QCOMPARE(thenResult, 0); @@ -2486,6 +2487,15 @@ void tst_QFuture::onFailed() QCOMPARE(checkpoint, 3); QCOMPARE(res, 0); } + + // onFailed on a canceled future + { + auto future = createCanceledFuture() + .then([](int) { return 42; }) + .onCanceled([] { return -1; }) + .onFailed([] { return -2; }); + QCOMPARE(future.result(), -1); + } } template @@ -2565,6 +2575,84 @@ void tst_QFuture::onFailedForMoveOnlyTypes() #endif // QT_NO_EXCEPTIONS +void tst_QFuture::onCanceled() +{ + // Canceled int future + { + auto future = createCanceledFuture().then([](int) { return 42; }).onCanceled([] { + return -1; + }); + QCOMPARE(future.result(), -1); + } + + // Canceled void future + { + int checkpoint = 0; + auto future = createCanceledFuture().then([&] { checkpoint = 42; }).onCanceled([&] { + checkpoint = -1; + }); + QCOMPARE(checkpoint, -1); + } + + // onCanceled propagates result + { + QFutureInterface 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 promise; + auto future = promise.future().then([](UniquePtr res) { return res; }).onCanceled([] { + return std::make_unique(-1); + }); + + promise.reportStarted(); + promise.reportAndMoveResult(std::make_unique(42)); + promise.reportFinished(); + QCOMPARE(*future.takeResult(), 42); + } + +#ifndef QT_NO_EXCEPTIONS + // onCanceled propagates exceptions + { + QFutureInterface 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() + .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) { QVERIFY(p.get() != nullptr);