summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2020-05-14 14:02:31 +0300
committerAndrei Golubev <andrei.golubev@qt.io>2020-06-09 17:21:38 +0300
commit385f0732d927f0eba8ecf990ee9bc19936475edd (patch)
treece8ba1adfb7252ad475885e6acc7ed256ec6b86f /src
parent20ba86262f39886629880fa1e07383f1e3e1d52e (diff)
Add QPromise implementation
QPromise and QFuture created from it share the same internal state (namely, QFutureInterface object). QPromise provides high-level management of the shared resource, ensuring thread-safe behavior on construction and destruction (also taking into account QFuture::waitForFinished() semantics). QFuture acts as a primary controller of QPromise via action initiating methods such as suspend() or cancel(). QPromise is equipped with methods to check the status, but the actual handling of QFuture action "requests" is user-defined. [ChangeLog][QtCore][QPromise] Added QPromise class to accompany QFuture. It allows one to communicate computation results and progress to the QFuture via a shared state. Task-number: QTBUG-81586 Change-Id: Ibab9681d35fe63754bf394ad0e7923e2683e2457 Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src')
-rw-r--r--src/corelib/thread/qfuture_impl.h4
-rw-r--r--src/corelib/thread/qfutureinterface.cpp36
-rw-r--r--src/corelib/thread/qfutureinterface.h9
-rw-r--r--src/corelib/thread/qpromise.h128
-rw-r--r--src/corelib/thread/thread.pri3
5 files changed, 179 insertions, 1 deletions
diff --git a/src/corelib/thread/qfuture_impl.h b/src/corelib/thread/qfuture_impl.h
index 82c89cfa38..883f8d4f09 100644
--- a/src/corelib/thread/qfuture_impl.h
+++ b/src/corelib/thread/qfuture_impl.h
@@ -66,6 +66,10 @@ enum class Launch { Sync, Async, Inherit };
namespace QtPrivate {
+template<class T, class U>
+using EnableIfSameOrConvertible = std::enable_if_t<std::is_same_v<T, U>
+ || std::is_convertible_v<T, U>>;
+
template<class T>
using EnableForVoid = std::enable_if_t<std::is_same_v<T, void>>;
diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp
index 05482c40c2..1785815cf3 100644
--- a/src/corelib/thread/qfutureinterface.cpp
+++ b/src/corelib/thread/qfutureinterface.cpp
@@ -259,6 +259,37 @@ void QFutureInterfaceBase::waitForResume()
d->pausedWaitCondition.wait(&d->m_mutex);
}
+void QFutureInterfaceBase::suspendIfRequested()
+{
+ const auto canSuspend = [] (int state) {
+ // can suspend only if 1) in any suspend-related state; 2) not canceled
+ return (state & suspendingOrSuspended) && !(state & Canceled);
+ };
+
+ // return early if possible to avoid taking the mutex lock.
+ {
+ const int state = d->state.loadRelaxed();
+ if (!canSuspend(state))
+ return;
+ }
+
+ QMutexLocker lock(&d->m_mutex);
+ const int state = d->state.loadRelaxed();
+ if (!canSuspend(state))
+ return;
+
+ // Note: expecting that Suspending and Suspended are mutually exclusive
+ if (!(state & Suspended)) {
+ // switch state in case this is the first invocation
+ switch_from_to(d->state, Suspending, Suspended);
+ d->sendCallOut(QFutureCallOutEvent(QFutureCallOutEvent::Suspended));
+ }
+
+ // decrease active thread count since this thread will wait.
+ const ThreadPoolThreadReleaser releaser(d->pool());
+ d->pausedWaitCondition.wait(&d->m_mutex);
+}
+
int QFutureInterfaceBase::progressValue() const
{
const QMutexLocker lock(&d->m_mutex);
@@ -361,6 +392,11 @@ bool QFutureInterfaceBase::queryState(State state) const
return d->state.loadRelaxed() & state;
}
+int QFutureInterfaceBase::loadState() const
+{
+ return d->state.loadRelaxed();
+}
+
void QFutureInterfaceBase::waitForResult(int resultIndex)
{
d->m_exceptionStore.throwPossibleException();
diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h
index c802be762b..75c4cae0ca 100644
--- a/src/corelib/thread/qfutureinterface.h
+++ b/src/corelib/thread/qfutureinterface.h
@@ -140,6 +140,7 @@ public:
bool isThrottled() const;
bool isResultReadyAt(int index) const;
bool isValid() const;
+ int loadState() const;
void cancel();
void setSuspended(bool suspend);
@@ -151,6 +152,7 @@ public:
bool waitForNextResult();
void waitForResult(int resultIndex);
void waitForResume();
+ void suspendIfRequested();
QMutex &mutex() const;
QtPrivate::ExceptionStore &exceptionStore();
@@ -233,6 +235,7 @@ public:
inline void reportResult(const T *result, int index = -1);
inline void reportAndMoveResult(T &&result, int index = -1);
+ inline void reportResult(T &&result, int index = -1);
inline void reportResult(const T &result, int index = -1);
inline void reportResults(const QVector<T> &results, int beginIndex = -1, int count = -1);
inline void reportFinished(const T *result);
@@ -285,6 +288,12 @@ void QFutureInterface<T>::reportAndMoveResult(T &&result, int index)
reportResultsReady(insertIndex, store.count());
}
+template<typename T>
+void QFutureInterface<T>::reportResult(T &&result, int index)
+{
+ reportAndMoveResult(std::move(result), index);
+}
+
template <typename T>
inline void QFutureInterface<T>::reportResult(const T &result, int index)
{
diff --git a/src/corelib/thread/qpromise.h b/src/corelib/thread/qpromise.h
new file mode 100644
index 0000000000..15aea16f29
--- /dev/null
+++ b/src/corelib/thread/qpromise.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QPROMISE_H
+#define QPROMISE_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qfuture.h>
+
+QT_REQUIRE_CONFIG(future);
+
+QT_BEGIN_NAMESPACE
+
+template<typename T>
+class QPromise
+{
+ static_assert (std::is_copy_constructible_v<T>
+ || std::is_move_constructible_v<T>
+ || std::is_same_v<T, void>,
+ "Type with copy or move constructors or type void is required");
+public:
+ QPromise() = default;
+ Q_DISABLE_COPY(QPromise)
+ QPromise(QPromise<T> &&other) : d(other.d)
+ {
+ // In constructor, there's no need to perform swap(). Assign new
+ // QFutureInterface to other.d instead which is slightly cheaper.
+ other.d = QFutureInterface<T>();
+ }
+ QPromise& operator=(QPromise<T> &&other)
+ {
+ swap(other);
+ return *this;
+ }
+ ~QPromise()
+ {
+ const int state = d.loadState();
+ // If QFutureInterface has no state, there is nothing to be done
+ if (state == static_cast<int>(QFutureInterfaceBase::State::NoState))
+ return;
+ // Otherwise, if computation is not finished at this point, cancel
+ // potential waits
+ if (!(state & QFutureInterfaceBase::State::Finished)) {
+ d.cancel();
+ reportFinished(); // required to finalize the state
+ }
+ }
+
+ // Core QPromise APIs
+ QFuture<T> future() const { return d.future(); }
+ template<typename U = T,
+ typename = QtPrivate::EnableForNonVoid<std::decay_t<U>>,
+ typename = QtPrivate::EnableIfSameOrConvertible<std::decay_t<U>, std::decay_t<T>>>
+ void addResult(U &&result, int index = -1)
+ {
+ d.reportResult(std::forward<U>(result), index);
+ }
+#ifndef QT_NO_EXCEPTIONS
+ void setException(const QException &e) { d.reportException(e); }
+ void setException(std::exception_ptr e) { d.reportException(e); }
+#endif
+ void reportStarted() { d.reportStarted(); }
+ void reportFinished() { d.reportFinished(); }
+
+ void suspendIfRequested() { d.suspendIfRequested(); }
+
+ bool isCanceled() const { return d.isCanceled(); }
+
+ // Progress methods
+ void setProgressRange(int minimum, int maximum) { d.setProgressRange(minimum, maximum); }
+ void setProgressValue(int progressValue) { d.setProgressValue(progressValue); }
+ void setProgressValueAndText(int progressValue, const QString &progressText)
+ {
+ d.setProgressValueAndText(progressValue, progressText);
+ }
+
+private:
+ mutable QFutureInterface<T> d = QFutureInterface<T>();
+
+ void swap(QPromise<T> &other)
+ {
+ // Note: copy operations are expensive! They trigger several atomic
+ // reference counts
+ auto tmp = this->d;
+ this->d = other.d;
+ other.d = tmp;
+ }
+};
+
+QT_END_NAMESPACE
+
+#endif // QPROMISE_H
diff --git a/src/corelib/thread/thread.pri b/src/corelib/thread/thread.pri
index e3d791fee7..d36c8011ef 100644
--- a/src/corelib/thread/thread.pri
+++ b/src/corelib/thread/thread.pri
@@ -72,7 +72,8 @@ qtConfig(future) {
thread/qfuturesynchronizer.h \
thread/qfuturewatcher.h \
thread/qfuturewatcher_p.h \
- thread/qresultstore.h
+ thread/qresultstore.h \
+ thread/qpromise.h
SOURCES += \
thread/qexception.cpp \