diff options
author | Sona Kurazyan <sona.kurazyan@qt.io> | 2020-05-06 17:42:21 +0200 |
---|---|---|
committer | Sona Kurazyan <sona.kurazyan@qt.io> | 2020-05-15 22:35:06 +0200 |
commit | 612f6999c81a500a024f128bdf739342d659754a (patch) | |
tree | c31990d9621f5b1c4754a4149222042daafea219 | |
parent | 54aa63be9b74e8de72db9efbe6809ab1a97b29a7 (diff) |
Add support of connecting signals to QFuture
Introduced QtFuture::connect(sender, signal) function returning a
QFuture object, which is resolved when the signal is emitted.
Task-number: QTBUG-81589
Change-Id: Idbe301eb247b468b9b34f3470c3359d6a7af2f3a
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp | 45 | ||||
-rw-r--r-- | src/corelib/thread/qfuture.qdoc | 47 | ||||
-rw-r--r-- | src/corelib/thread/qfuture_impl.h | 78 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 87 |
4 files changed, 256 insertions, 1 deletions
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 66fa62f6b3..0580f142d7 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp @@ -175,3 +175,48 @@ try { // Handle the exception } //! [9] + +//! [10] +class Object : public QObject +{ + Q_OBJECT + ... +signals: + void noArgSignal(); + void singleArgSignal(int value); + void multipleArgs(int value1, double value2, const QString &value3); +}; +//! [10] + +//! [11] +Object object; +QFuture<void> voidFuture = QtFuture::connect(&object, &Object::noArgSignal); +QFuture<int> intFuture = QtFuture::connect(&object, &Object::singleArgSignal); + +using Args = std::tuple<int, double, QString>; +QFuture<Args> tupleFuture = QtFuture::connect(&object, &Object::multipleArgs) +//! [11] + +//! [12] +QtFuture::connect(&object, &Object::singleArgSignal).then([](int value) { + // do something with the value +}); +//! [12] + +//! [13] +QtFuture::connect(&object, &Object::singleArgSignal).then(QtFuture::Launch::Async, [](int value) { + // this will run in a new thread +}); +//! [13] + +//! [14] +QtFuture::connect(&object, &Object::singleArgSignal).then([](int value) { + ... + throw std::exception(); + ... +}).onFailed([](const std::exception &e) { + // handle the exception +}).onFailed([] { + // handle other exceptions +}); +//! [14] diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc index 6fb7ab032a..f977a48f96 100644 --- a/src/corelib/thread/qfuture.qdoc +++ b/src/corelib/thread/qfuture.qdoc @@ -103,7 +103,13 @@ To interact with running tasks using signals and slots, use QFutureWatcher. - \sa QFutureWatcher, {Qt Concurrent} + You can also use QtFuture::connect to connect signals to a QFuture object + which will be resolved when a signal is emitted. This allows working with + signals like with QFuture objects. For example, if you combine it with then(), + you can attach multiple continuations to a signal, which are invoked in the + same thread or a new thread. + + \sa QtFuture::connect(), QFutureWatcher, {Qt Concurrent} */ /*! \fn template <typename T> QFuture<T>::QFuture() @@ -799,6 +805,45 @@ */ +/*! \fn template<class Sender, class Signal> static QFuture<ArgsType<Signal>> QtFuture::connect(Sender *sender, Signal signal) + + Creates and returns a QFuture which will become available when the \a sender emits + the \a signal. If the \a signal takes no arguments, a QFuture<void> is returned. If + the \a signal takes a single argument, the resulted QFuture will be filled with the + signal's argument value. If the \a signal takes multiple arguments, the resulted QFuture + is filled with std::tuple storing the values of signal's arguments. If the \a sender + is destroyed before the \a signal is emitted, the resulted QFuture will be canceled. + + For example, let's say we have the following object: + + \snippet code/src_corelib_thread_qfuture.cpp 10 + + We can connect its signals to QFuture objects in the following way: + + \snippet code/src_corelib_thread_qfuture.cpp 11 + + We can also chain continuations to be run when a signal is emitted: + + \snippet code/src_corelib_thread_qfuture.cpp 12 + + You can also start the continuation in a new thread or a custom thread pool + using QtFuture::Launch policies. For example: + + \snippet code/src_corelib_thread_qfuture.cpp 13 + + Throwing an exception from a slot invoked by Qt's signal-slot connection + is considered to be an undefined behavior, if it is not handled within the + slot. But with QFuture::connect(), you can throw and handle exceptions from + the continuations: + + \snippet code/src_corelib_thread_qfuture.cpp 14 + + \note The connected future will be fulfilled only once, when the signal is + emitted for the first time. + + \sa QFuture, QFuture::then() +*/ + /*! \fn template<class T> template<class Function> QFuture<typename QFuture<T>::ResultType<Function>> QFuture<T>::then(Function &&function) \since 6.0 diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h index 5a4ffce1c7..0e0cef0dbf 100644 --- a/src/corelib/thread/qfuture_impl.h +++ b/src/corelib/thread/qfuture_impl.h @@ -118,6 +118,12 @@ struct ArgsType<Arg, Args...> { using First = Arg; static const bool HasExtraArgs = (sizeof...(Args) > 0); + using AllArgs = + std::conditional_t<HasExtraArgs, std::tuple<std::decay_t<Arg>, std::decay_t<Args>...>, + std::decay_t<Arg>>; + + template<class Class, class Callable> + static const bool CanInvokeWithArgs = std::is_invocable_v<Callable, Class, Arg, Args...>; }; template<> @@ -125,6 +131,10 @@ struct ArgsType<> { using First = void; static const bool HasExtraArgs = false; + using AllArgs = void; + + template<class Class, class Callable> + static const bool CanInvokeWithArgs = std::is_invocable_v<Callable, Class>; }; template<typename F> @@ -167,6 +177,21 @@ struct ArgResolver<R (Class::*)(Args...) const noexcept> : public ArgsType<Args. { }; +template<class Class, class Callable> +using EnableIfInvocable = std::enable_if_t< + QtPrivate::ArgResolver<Callable>::template CanInvokeWithArgs<Class, Callable>>; + +template<class> +struct isTuple : std::false_type +{ +}; +template<class... T> +struct isTuple<std::tuple<T...>> : std::true_type +{ +}; +template<class T> +inline constexpr bool isTupleV = isTuple<T>::value; + template<typename Function, typename ResultType, typename ParentResultType> class Continuation { @@ -564,4 +589,57 @@ void FailureHandler<Function, ResultType>::handleAllExceptions() } // namespace QtPrivate +namespace QtFuture { + +template<class Signal> +using ArgsType = typename QtPrivate::ArgResolver<Signal>::AllArgs; + +template<class Sender, class Signal, typename = QtPrivate::EnableIfInvocable<Sender, Signal>> +static QFuture<ArgsType<Signal>> connect(Sender *sender, Signal signal) +{ + using ArgsType = ArgsType<Signal>; + QFutureInterface<ArgsType> promise; + promise.reportStarted(); + + using Connections = std::pair<QMetaObject::Connection, QMetaObject::Connection>; + auto connections = std::make_shared<Connections>(); + + if constexpr (std::is_void_v<ArgsType>) { + connections->first = + QObject::connect(sender, signal, sender, [promise, connections]() mutable { + promise.reportFinished(); + QObject::disconnect(connections->first); + QObject::disconnect(connections->second); + }); + } else if constexpr (QtPrivate::isTupleV<ArgsType>) { + connections->first = QObject::connect(sender, signal, sender, + [promise, connections](auto... values) mutable { + promise.reportResult(std::make_tuple(values...)); + promise.reportFinished(); + QObject::disconnect(connections->first); + QObject::disconnect(connections->second); + }); + } else { + connections->first = QObject::connect(sender, signal, sender, + [promise, connections](ArgsType value) mutable { + promise.reportResult(value); + promise.reportFinished(); + QObject::disconnect(connections->first); + QObject::disconnect(connections->second); + }); + } + + connections->second = + QObject::connect(sender, &QObject::destroyed, sender, [promise, connections]() mutable { + promise.reportCanceled(); + promise.reportFinished(); + QObject::disconnect(connections->first); + QObject::disconnect(connections->second); + }); + + return promise.future(); +} + +} // namespace QtFuture + QT_END_NAMESPACE diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 8d4ae41fd5..f61aa1361e 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -53,6 +53,26 @@ struct ResultStoreInt : QtPrivate::ResultStoreBase ~ResultStoreInt() { clear<int>(); } }; +class SenderObject : public QObject +{ + Q_OBJECT + +public: + void emitNoArg() { emit noArgSignal(); } + void emitIntArg(int value) { emit intArgSignal(value); } + void emitConstRefArg(const QString &value) { emit constRefArg(value); } + void emitMultipleArgs(int value1, double value2, const QString &value3) + { + emit multipleArgs(value1, value2, value3); + } + +signals: + void noArgSignal(); + void intArgSignal(int value); + void constRefArg(const QString &value); + void multipleArgs(int value1, double value2, const QString &value3); +}; + class LambdaThread : public QThread { public: @@ -118,6 +138,8 @@ private slots: void resultsReadyAt(); void takeResultWorksForTypesWithoutDefaultCtor(); void canceledFutureIsNotValid(); + void signalConnect(); + private: using size_type = std::vector<int>::size_type; @@ -2781,5 +2803,70 @@ void tst_QFuture::canceledFutureIsNotValid() QVERIFY(!f.isValid()); } +void tst_QFuture::signalConnect() +{ + // No arg + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::noArgSignal).then([&] { return true; }); + sender.emitNoArg(); + QCOMPARE(future.result(), true); + } + + // One arg + { + SenderObject sender; + auto future = QtFuture::connect(&sender, &SenderObject::intArgSignal).then([](int value) { + return value; + }); + sender.emitIntArg(42); + QCOMPARE(future.result(), 42); + } + + // Const ref arg + { + SenderObject sender; + auto future = + QtFuture::connect(&sender, &SenderObject::constRefArg).then([](QString value) { + return value; + }); + sender.emitConstRefArg(QString("42")); + QCOMPARE(future.result(), "42"); + } + + // Multiple args + { + SenderObject sender; + using TupleArgs = std::tuple<int, double, QString>; + auto future = + QtFuture::connect(&sender, &SenderObject::multipleArgs).then([](TupleArgs values) { + return values; + }); + sender.emitMultipleArgs(42, 42.5, "42"); + auto result = future.result(); + QCOMPARE(std::get<0>(result), 42); + QCOMPARE(std::get<1>(result), 42.5); + QCOMPARE(std::get<2>(result), "42"); + } + + // Sender destroyed + { + SenderObject *sender = new SenderObject(); + + auto future = QtFuture::connect(sender, &SenderObject::intArgSignal); + + QSignalSpy spy(sender, &QObject::destroyed); + sender->deleteLater(); + + // emit the signal when sender is being destroyed + QObject::connect(sender, &QObject::destroyed, [sender] { sender->emitIntArg(42); }); + spy.wait(); + + QVERIFY(future.isCanceled()); + QVERIFY(!future.isValid()); + } +} + QTEST_MAIN(tst_QFuture) #include "tst_qfuture.moc" |