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:
parent
54aa63be9b
commit
612f6999c8
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user