summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorVitaly Fanaskov <vitaly.fanaskov@qt.io>2020-03-11 18:07:03 +0100
committerVitaly Fanaskov <vitaly.fanaskov@qt.io>2020-03-29 20:44:32 +0100
commit5a0d4f3313157d2fe48e9d159968bed6883eb5f8 (patch)
treed7366ac16fdbe83ff4e8006577f9987404ae2742 /src
parentd975ad4ed728553b765c61f38c1e0df899187cf5 (diff)
QtConcurrent: add fluent interface to configure a task before run
Task-number: QTBUG-82950 Change-Id: I449da938b6b501a7646b3425edde5c880d6ca87e Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Paul Wicking <paul.wicking@qt.io> Reviewed-by: Mikhail Svetkin <mikhail.svetkin@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/concurrent/CMakeLists.txt2
-rw-r--r--src/concurrent/concurrent.pro6
-rw-r--r--src/concurrent/doc/snippets/code/src_concurrent_qtconcurrenttask.cpp127
-rw-r--r--src/concurrent/doc/src/qtconcurrent-index.qdoc7
-rw-r--r--src/concurrent/qtaskbuilder.h160
-rw-r--r--src/concurrent/qtaskbuilder.qdoc82
-rw-r--r--src/concurrent/qtconcurrentrun.h3
-rw-r--r--src/concurrent/qtconcurrentrunbase.h17
-rw-r--r--src/concurrent/qtconcurrentstoredfunctioncall.h11
-rw-r--r--src/concurrent/qtconcurrenttask.h75
-rw-r--r--src/concurrent/qtconcurrenttask.qdoc156
11 files changed, 635 insertions, 11 deletions
diff --git a/src/concurrent/CMakeLists.txt b/src/concurrent/CMakeLists.txt
index f46261f3b2..b5674d1f94 100644
--- a/src/concurrent/CMakeLists.txt
+++ b/src/concurrent/CMakeLists.txt
@@ -22,6 +22,8 @@ qt_add_module(Concurrent
qtconcurrentrunbase.h
qtconcurrentstoredfunctioncall.h
qtconcurrentthreadengine.cpp qtconcurrentthreadengine.h
+ qtaskbuilder.h
+ qtconcurrenttask.h
DEFINES
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
diff --git a/src/concurrent/concurrent.pro b/src/concurrent/concurrent.pro
index 18510e38a1..6e9b22d2e4 100644
--- a/src/concurrent/concurrent.pro
+++ b/src/concurrent/concurrent.pro
@@ -15,7 +15,7 @@ SOURCES += \
qtconcurrentmap.cpp \
qtconcurrentrun.cpp \
qtconcurrentthreadengine.cpp \
- qtconcurrentiteratekernel.cpp \
+ qtconcurrentiteratekernel.cpp
HEADERS += \
qtconcurrent_global.h \
@@ -32,7 +32,9 @@ HEADERS += \
qtconcurrentrun.h \
qtconcurrentrunbase.h \
qtconcurrentstoredfunctioncall.h \
- qtconcurrentthreadengine.h
+ qtconcurrentthreadengine.h \
+ qtaskbuilder.h \
+ qtconcurrenttask.h
# private headers
HEADERS += \
diff --git a/src/concurrent/doc/snippets/code/src_concurrent_qtconcurrenttask.cpp b/src/concurrent/doc/snippets/code/src_concurrent_qtconcurrenttask.cpp
new file mode 100644
index 0000000000..bec45ba3bf
--- /dev/null
+++ b/src/concurrent/doc/snippets/code/src_concurrent_qtconcurrenttask.cpp
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//! [0]
+QtConcurrent::task([]{ qDebug("Hello, world!"); }).spawn();
+//! [0]
+
+//! [1]
+auto task = [](const QString &s){ qDebug() << ("Hello, " + s); };
+QtConcurrent::task(std::move(task))
+ .withArguments("world!")
+ .spawn();
+//! [1]
+
+//! [2]
+QString s("Hello, ");
+QtConcurrent::task([](QString &s){ s.append("world!"); })
+ .withArguments(std::ref(s))
+ .spawn();
+//! [2]
+
+//! [3]
+auto future = QtConcurrent::task([]{ return 42; }).spawn();
+auto result = future.result(); // result == 42
+//! [3]
+
+//! [4]
+std::is_invocable_v<std::decay_t<Task>, std::decay_t<Args>...>
+//! [4]
+
+//! [5]
+QVariant value(42);
+auto result = QtConcurrent::task(&qvariant_cast<int>)
+ .withArguments(value)
+ .spawn()
+ .result(); // result == 42
+//! [5]
+
+//! [6]
+QString result("Hello, world!");
+
+QtConcurrent::task(&QString::chop)
+ .withArguments(&result, 8)
+ .spawn()
+ .waitForFinished(); // result == "Hello"
+//! [6]
+
+//! [7]
+auto result = QtConcurrent::task(std::plus<int>())
+ .withArguments(40, 2)
+ .spawn()
+ .result() // result == 42
+//! [7]
+
+//! [8]
+struct CallableWithState
+{
+ void operator()(int newState) { state = newState; }
+
+ // ...
+};
+
+// ...
+
+CallableWithState object;
+
+QtConcurrent::task(std::ref(object))
+ .withArguments(42)
+ .spawn()
+ .waitForFinished(); // The object's state is set to 42
+//! [8]
+
+//! [9]
+QThreadPool pool;
+QtConcurrent::task([]{ return 42; }).onThreadPool(pool).spawn();
+//! [9]
+
+//! [10]
+QtConcurrent::task([]{ return 42; }).withPriority(10).spawn();
+//! [10]
diff --git a/src/concurrent/doc/src/qtconcurrent-index.qdoc b/src/concurrent/doc/src/qtconcurrent-index.qdoc
index eb5cd334dd..666eec533b 100644
--- a/src/concurrent/doc/src/qtconcurrent-index.qdoc
+++ b/src/concurrent/doc/src/qtconcurrent-index.qdoc
@@ -79,6 +79,13 @@
another thread.
\endlist
+ \li \l {Concurrent Task}
+ \list
+ \li \l {QtConcurrent::task}{QtConcurrent::task()} creates an instance
+ of QtConcurrent::QTaskBuilder. This object can be used for adjusting
+ parameters and for kicking off a task in a separate thread.
+ \endlist
+
\li QFuture represents the result of an asynchronous computation.
\li QFutureIterator allows iterating through results available via QFuture.
diff --git a/src/concurrent/qtaskbuilder.h b/src/concurrent/qtaskbuilder.h
new file mode 100644
index 0000000000..5fc2bccfb5
--- /dev/null
+++ b/src/concurrent/qtaskbuilder.h
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtConcurrent 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 QTBASE_QTTASKBUILDER_H
+#define QTBASE_QTTASKBUILDER_H
+
+#include <memory>
+
+#if !defined(QT_NO_CONCURRENT) || defined(Q_CLANG_QDOC)
+
+#include <QtConcurrent/qtconcurrentstoredfunctioncall.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_CLANG_QDOC
+
+namespace QtConcurrent {
+
+using InvokeResultType = int;
+
+template <class Task, class ...Args>
+class QTaskBuilder
+{
+public:
+ [[nodiscard]]
+ QFuture<InvokeResultType> spawn();
+
+ template <class ...ExtraArgs>
+ [[nodiscard]]
+ QTaskBuilder<Task, ExtraArgs...> withArguments(ExtraArgs &&...args);
+
+ [[nodiscard]]
+ QTaskBuilder<Task, Args...> &onThreadPool(QThreadPool &newThreadPool);
+
+ [[nodiscard]]
+ QTaskBuilder<Task, Args...> &withPriority(int newPriority);
+};
+
+} // namespace QtConcurrent
+
+#else
+
+namespace QtConcurrent {
+
+template <class Task, class ...Args>
+class QTaskBuilder
+{
+public:
+ [[nodiscard]]
+ auto spawn()
+ {
+ return (new StoredFunctionCall<Task, Args...>(std::move(taskWithArgs)))
+ ->start(startParameters);
+ }
+
+ template <class ...ExtraArgs>
+ [[nodiscard]]
+ constexpr auto withArguments(ExtraArgs &&...args)
+ {
+ static_assert(std::tuple_size_v<TaskWithArgs> == 1,
+ "This function cannot be invoked if "
+ "arguments have already been passed.");
+
+ static_assert(sizeof...(ExtraArgs) >= 1,
+ "One or more arguments must be passed.");
+
+ // We have to re-create a builder, because the type has changed
+ return QTaskBuilder<Task, ExtraArgs...>(
+ startParameters,
+ std::get<0>(std::move(taskWithArgs)),
+ std::forward<ExtraArgs>(args)...
+ );
+ }
+
+ [[nodiscard]]
+ constexpr auto &onThreadPool(QThreadPool &newThreadPool)
+ {
+ startParameters.threadPool = &newThreadPool;
+ return *this;
+ }
+
+ [[nodiscard]]
+ constexpr auto &withPriority(int newPriority)
+ {
+ startParameters.priority = newPriority;
+ return *this;
+ }
+
+protected: // Methods
+ constexpr explicit QTaskBuilder(Task &&task, Args &&...arguments)
+ : taskWithArgs{std::forward<Task>(task), std::forward<Args>(arguments)...}
+ {}
+
+ constexpr QTaskBuilder(
+ const TaskStartParameters &parameters, Task &&task, Args &&...arguments)
+ : taskWithArgs{std::forward<Task>(task), std::forward<Args>(arguments)...}
+ , startParameters{parameters}
+ {}
+
+private: // Methods
+ // Required for creating a builder from "task" function
+ template <class T>
+ friend constexpr auto task(T &&t);
+
+ // Required for creating a new builder from "withArguments" function
+ template <class T, class ...A>
+ friend class QTaskBuilder;
+
+private: // Data
+ using TaskWithArgs = DecayedTuple<Task, Args...>;
+
+ TaskWithArgs taskWithArgs;
+ TaskStartParameters startParameters;
+};
+
+} // namespace QtConcurrent
+
+#endif // Q_CLANG_QDOC
+
+QT_END_NAMESPACE
+
+#endif // !defined(QT_NO_CONCURRENT)
+
+#endif //QTBASE_QTTASKBUILDER_H
diff --git a/src/concurrent/qtaskbuilder.qdoc b/src/concurrent/qtaskbuilder.qdoc
new file mode 100644
index 0000000000..8352cae17a
--- /dev/null
+++ b/src/concurrent/qtaskbuilder.qdoc
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \class QtConcurrent::QTaskBuilder
+ \inmodule QtConcurrent
+ \brief The QTaskBuilder class is used for adjusting task parameters.
+ \since 6.0
+
+ \ingroup thread
+
+ It's not possible to create an object of this class manually. See
+ \l {Concurrent Task} for more details and usage examples.
+*/
+
+/*!
+ \fn template <class Task, class ...Args> [[nodiscard]] QFuture<InvokeResultType> QtConcurrent::QTaskBuilder<Task, Args...>::spawn()
+
+ Runs the task in a separate thread and returns a future object immediately.
+ This is a non-blocking call. The task might not start immediately.
+*/
+
+/*!
+ \fn template <class Task, class ...Args> template <class ...ExtraArgs> [[nodiscard]] QTaskBuilder<Task, ExtraArgs...> QtConcurrent::QTaskBuilder<Task, Args...>::withArguments(ExtraArgs &&...args)
+
+ Sets the arguments \a args the task will be invoked with. The code is ill-formed
+ (causes compilation errors) if:
+ \list
+ \li This function is invoked more than once.
+ \li The arguments count is zero.
+ \endlist
+*/
+
+/*!
+ \fn template <class Task, class ...Args> [[nodiscard]] QTaskBuilder<Task, Args...> &QtConcurrent::QTaskBuilder<Task, Args...>::onThreadPool(QThreadPool &newThreadPool)
+
+ Sets the thread pool \a newThreadPool that the task will be invoked on.
+*/
+
+/*!
+ \fn template <class Task, class ...Args> [[nodiscard]] QTaskBuilder<Task, Args...> &QtConcurrent::QTaskBuilder<Task, Args...>::withPriority(int newPriority)
+
+ Sets the priority \a newPriority that the task will be invoked with.
+*/
+
+/*!
+ \typedef InvokeResultType
+ \relates QtConcurrent::QTaskBuilder
+
+ The simplified definition of this type looks like this:
+ \code
+ template <class Task, class ...Args>
+ using InvokeResultType = std::invoke_result_t<std::decay_t<Task>, std::decay_t<Args>...>;
+ \endcode
+
+ The real implementation also contains a compile-time check for
+ whether the task can be invoked with the specified arguments or not.
+*/
diff --git a/src/concurrent/qtconcurrentrun.h b/src/concurrent/qtconcurrentrun.h
index 15a207be33..4f583a343c 100644
--- a/src/concurrent/qtconcurrentrun.h
+++ b/src/concurrent/qtconcurrentrun.h
@@ -72,7 +72,8 @@ template <class Function, class ...Args>
auto run(QThreadPool *pool, Function &&f, Args &&...args)
{
return (new StoredFunctionCall<Function, Args...>(
- std::forward<Function>(f), std::forward<Args>(args)...))->start(pool);
+ std::forward<Function>(f), std::forward<Args>(args)...))
+ ->start(pool);
}
template <class Function, class ...Args>
diff --git a/src/concurrent/qtconcurrentrunbase.h b/src/concurrent/qtconcurrentrunbase.h
index aaa1245856..e84c0cdb67 100644
--- a/src/concurrent/qtconcurrentrunbase.h
+++ b/src/concurrent/qtconcurrentrunbase.h
@@ -69,25 +69,34 @@ struct SelectSpecialization<void>
struct Type { typedef Void type; };
};
+struct TaskStartParameters
+{
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ int priority = 0;
+};
+
template <typename T>
class RunFunctionTaskBase : public QFutureInterface<T> , public QRunnable
{
public:
QFuture<T> start()
{
- return start(QThreadPool::globalInstance());
+ return start(TaskStartParameters());
}
- QFuture<T> start(QThreadPool *pool)
+ QFuture<T> start(const TaskStartParameters &parameters)
{
- this->setThreadPool(pool);
+ this->setThreadPool(parameters.threadPool);
this->setRunnable(this);
this->reportStarted();
QFuture<T> theFuture = this->future();
- pool->start(this, /*m_priority*/ 0);
+ parameters.threadPool->start(this, parameters.priority);
return theFuture;
}
+ // For backward compatibility
+ QFuture<T> start(QThreadPool *pool) { return start({pool, 0}); }
+
void run() override {}
virtual void runFunctor() = 0;
};
diff --git a/src/concurrent/qtconcurrentstoredfunctioncall.h b/src/concurrent/qtconcurrentstoredfunctioncall.h
index e2d0e3de0f..f64648d7e9 100644
--- a/src/concurrent/qtconcurrentstoredfunctioncall.h
+++ b/src/concurrent/qtconcurrentstoredfunctioncall.h
@@ -48,7 +48,6 @@
QT_BEGIN_NAMESPACE
-
#ifndef Q_QDOC
namespace QtConcurrent {
@@ -65,16 +64,20 @@ struct InvokeResult
template <class Function, class ...Args>
using InvokeResultType = typename InvokeResult<Function, Args...>::Type;
+template <class ...Types>
+using DecayedTuple = std::tuple<std::decay_t<Types>...>;
+
template <class Function, class ...Args>
struct StoredFunctionCall : public RunFunctionTask<InvokeResultType<Function, Args...>>
{
- template <class ...Types>
- using DecayedTuple = std::tuple<std::decay_t<Types>...>;
-
StoredFunctionCall(Function &&f, Args &&...args)
: data{std::forward<Function>(f), std::forward<Args>(args)...}
{}
+ StoredFunctionCall(DecayedTuple<Function, Args...> &&_data)
+ : data(std::move(_data))
+ {}
+
void runFunctor() override
{
using Indexes =
diff --git a/src/concurrent/qtconcurrenttask.h b/src/concurrent/qtconcurrenttask.h
new file mode 100644
index 0000000000..ff55df7748
--- /dev/null
+++ b/src/concurrent/qtconcurrenttask.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtConcurrent 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 QTCONCURRENTTASK_H
+#define QTCONCURRENTTASK_H
+
+#if !defined(QT_NO_CONCURRENT)
+
+#include <QtConcurrent/qtaskbuilder.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_CLANG_QDOC
+
+namespace QtConcurrent {
+
+template <class Task>
+[[nodiscard]]
+QTaskBuilder<Task> task(Task &&task);
+
+} // namespace QtConcurrent
+
+#else
+
+namespace QtConcurrent {
+
+template <class Task>
+[[nodiscard]]
+constexpr auto task(Task &&t) { return QTaskBuilder(std::forward<Task>(t)); }
+
+} // namespace QtConcurrent
+
+#endif // Q_CLANG_QDOC
+
+QT_END_NAMESPACE
+
+#endif // !defined(QT_NO_CONCURRENT)
+
+#endif // QTCONCURRENTTASK_H
diff --git a/src/concurrent/qtconcurrenttask.qdoc b/src/concurrent/qtconcurrenttask.qdoc
new file mode 100644
index 0000000000..e25e485bd1
--- /dev/null
+++ b/src/concurrent/qtconcurrenttask.qdoc
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qtconcurrenttask.html
+ \title Concurrent Task
+ \ingroup thread
+
+ QtConcurrent::task provides an alternative interface for running a
+ task in a separate thread. The return value of the function is made
+ available through the QFuture API.
+
+ If you want to just run a function in a separate thread without adjusting
+ any parameters, use QtConcurrent::run as that lets you write less code.
+ The QtConcurrent::task is designed for cases where you need to perform
+ extra configurations steps.
+
+ This function is a part of the \l {Qt Concurrent} framework.
+
+ \section1 Fluent interface
+
+ The QtConcurrent::task returns an instance of an auxiliary class called
+ QtConcurrent::QTaskBuilder. Normally, you don't need to create an instance
+ of this class manually. The QtConcurrent::QTaskBuilder provides an interface
+ to adjust different task parameters in a chain-like manner. This approach
+ is known as a
+ \l {https://en.wikipedia.org/wiki/Fluent_interface}{fluent interface}.
+
+ You can just set the parameters you need and then kick a task off.
+ In order to finalize the configuration of a task you must invoke
+ QtConcurrent::QTaskBuilder::spawn. This function is non-blocking (i.e.
+ returns a future object immediately), but it's not guaranteed that the
+ task starts immediately. You can use the QFuture and QFutureWatcher classes
+ to monitor the status of the task.
+
+ See more examples and explanations below.
+
+ \section1 Running a task in a separate thread
+
+ To run a function in another thread, use QtConcurrent::QTaskBuilder::spawn:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 0
+
+ This will run a lambda function in a separate thread obtained from
+ the default QThreadPool.
+
+ \section1 Passing arguments to the task
+
+ Invoking a function with arguments is done by passing them to
+ QtConcurrent::QTaskBuilder::withArguments:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 1
+
+ A copy of each argument is made at the point where
+ QtConcurrent::QTaskBuilder::withArguments is called, and these values
+ are passed to the thread when it begins executing the task. Changes made
+ to the arguments after calling QtConcurrent::QTaskBuilder::withArguments
+ are not visible to the thread.
+
+ If you want to run a function that accepts arguments by reference, you
+ should use \l {https://en.cppreference.com/w/cpp/utility/functional/ref}
+ {std::ref/cref} auxiliary functions. These functions create thin wrappers
+ around passed arguments:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 2
+
+ Make sure that all wrapped objects live long enough. It is possible to
+ get undefined behavior if a task outlives the object wrapped by
+ std::ref/cref.
+
+ \section1 Returning values from the task
+
+ You can obtain the result of a task with the QFuture API:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 3
+
+ Note that QFuture::result() is a blocking call, it waits for the
+ result to become available. Use QFutureWatcher to get a notification
+ when the task has finished execution and the result is available.
+
+ In case you want to pass a result to another asynchronous task, you can
+ use QFuture::then() to create a chain of dependent tasks. See the QFuture
+ documentation for more details.
+
+ \section1 Additional API features
+
+ \section2 Using different types of callable objects
+
+ Strictly speaking, you can use any type of tasks and arguments that
+ satisfy the following condition:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 4
+
+ You can use a free function:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 5
+
+ You can use a member function:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 6
+
+ You can use a callable object with an operator():
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 7
+
+ If you want to use an existing callable object, you need to either
+ copy/move it to QtConcurrent::task or wrap it with std::ref/cref:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 8
+
+ \section2 Using custom thread pool
+
+ You can specify a custom thread pool:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 9
+
+ \section2 Setting priority for a task
+
+ You can set the priority for a task:
+
+ \snippet code/src_concurrent_qtconcurrenttask.cpp 10
+*/
+
+/*!
+ \fn template <typename Task> [[nodiscard]] QTaskBuilder<Task> QtConcurrent::task(Task &&task);
+ \since 6.0
+
+ Creates an instance of QtConcurrent::QTaskBuilder. This object can be used
+ to adjust some parameters and run \a task in a separate thread.
+
+ \sa {Concurrent Task}, QtConcurrent::QTaskBuilder
+*/