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>
This commit is contained in:
Sona Kurazyan 2020-05-06 17:42:21 +02:00
parent 54aa63be9b
commit 612f6999c8
4 changed files with 256 additions and 1 deletions

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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"