summaryrefslogtreecommitdiffstats
path: root/src/corelib
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2020-05-06 17:42:21 +0200
committerSona Kurazyan <sona.kurazyan@qt.io>2020-05-15 22:35:06 +0200
commit612f6999c81a500a024f128bdf739342d659754a (patch)
treec31990d9621f5b1c4754a4149222042daafea219 /src/corelib
parent54aa63be9b74e8de72db9efbe6809ab1a97b29a7 (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>
Diffstat (limited to 'src/corelib')
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp45
-rw-r--r--src/corelib/thread/qfuture.qdoc47
-rw-r--r--src/corelib/thread/qfuture_impl.h78
3 files changed, 169 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