diff options
author | Vitaly Fanaskov <[email protected]> | 2020-03-11 18:07:03 +0100 |
---|---|---|
committer | Vitaly Fanaskov <[email protected]> | 2020-03-29 20:44:32 +0100 |
commit | 5a0d4f3313157d2fe48e9d159968bed6883eb5f8 (patch) | |
tree | d7366ac16fdbe83ff4e8006577f9987404ae2742 | |
parent | d975ad4ed728553b765c61f38c1e0df899187cf5 (diff) |
QtConcurrent: add fluent interface to configure a task before run
Task-number: QTBUG-82950
Change-Id: I449da938b6b501a7646b3425edde5c880d6ca87e
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Paul Wicking <[email protected]>
Reviewed-by: Mikhail Svetkin <[email protected]>
-rw-r--r-- | src/concurrent/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/concurrent/concurrent.pro | 6 | ||||
-rw-r--r-- | src/concurrent/doc/snippets/code/src_concurrent_qtconcurrenttask.cpp | 127 | ||||
-rw-r--r-- | src/concurrent/doc/src/qtconcurrent-index.qdoc | 7 | ||||
-rw-r--r-- | src/concurrent/qtaskbuilder.h | 160 | ||||
-rw-r--r-- | src/concurrent/qtaskbuilder.qdoc | 82 | ||||
-rw-r--r-- | src/concurrent/qtconcurrentrun.h | 3 | ||||
-rw-r--r-- | src/concurrent/qtconcurrentrunbase.h | 17 | ||||
-rw-r--r-- | src/concurrent/qtconcurrentstoredfunctioncall.h | 11 | ||||
-rw-r--r-- | src/concurrent/qtconcurrenttask.h | 75 | ||||
-rw-r--r-- | src/concurrent/qtconcurrenttask.qdoc | 156 | ||||
-rw-r--r-- | tests/auto/concurrent/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/concurrent/concurrent.pro | 3 | ||||
-rw-r--r-- | tests/auto/concurrent/qtconcurrenttask/CMakeLists.txt | 6 | ||||
-rw-r--r-- | tests/auto/concurrent/qtconcurrenttask/qtconcurrenttask.pro | 4 | ||||
-rw-r--r-- | tests/auto/concurrent/qtconcurrenttask/tst_qtconcurrenttask.cpp | 160 |
16 files changed, 808 insertions, 12 deletions
diff --git a/src/concurrent/CMakeLists.txt b/src/concurrent/CMakeLists.txt index f46261f3b26..b5674d1f944 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 18510e38a18..6e9b22d2e46 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 00000000000..bec45ba3bf3 --- /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 eb5cd334dd4..666eec533bf 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 00000000000..5fc2bccfb53 --- /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 ¶meters, 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 00000000000..8352cae17a8 --- /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 15a207be33c..4f583a343c9 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 aaa1245856b..e84c0cdb670 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 ¶meters) { - 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 e2d0e3de0fd..f64648d7e96 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 00000000000..ff55df7748b --- /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 00000000000..e25e485bd11 --- /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 +*/ diff --git a/tests/auto/concurrent/CMakeLists.txt b/tests/auto/concurrent/CMakeLists.txt index ae1e256bbd4..2ef8cfd1282 100644 --- a/tests/auto/concurrent/CMakeLists.txt +++ b/tests/auto/concurrent/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(qtconcurrentmap) add_subdirectory(qtconcurrentmedian) add_subdirectory(qtconcurrentrun) add_subdirectory(qtconcurrentthreadengine) +add_subdirectory(qtconcurrenttask) diff --git a/tests/auto/concurrent/concurrent.pro b/tests/auto/concurrent/concurrent.pro index e67c51aae3e..ad3231ff904 100644 --- a/tests/auto/concurrent/concurrent.pro +++ b/tests/auto/concurrent/concurrent.pro @@ -5,5 +5,6 @@ SUBDIRS=\ qtconcurrentmap \ qtconcurrentmedian \ qtconcurrentrun \ - qtconcurrentthreadengine + qtconcurrentthreadengine \ + qtconcurrenttask diff --git a/tests/auto/concurrent/qtconcurrenttask/CMakeLists.txt b/tests/auto/concurrent/qtconcurrenttask/CMakeLists.txt new file mode 100644 index 00000000000..d55a0536cfa --- /dev/null +++ b/tests/auto/concurrent/qtconcurrenttask/CMakeLists.txt @@ -0,0 +1,6 @@ +add_qt_test(tst_qtconcurrenttask + SOURCES + tst_qtconcurrenttask.cpp + PUBLIC_LIBRARIES + Qt::Concurrent +) diff --git a/tests/auto/concurrent/qtconcurrenttask/qtconcurrenttask.pro b/tests/auto/concurrent/qtconcurrenttask/qtconcurrenttask.pro new file mode 100644 index 00000000000..4404efa0b34 --- /dev/null +++ b/tests/auto/concurrent/qtconcurrenttask/qtconcurrenttask.pro @@ -0,0 +1,4 @@ +CONFIG += testcase +TARGET = tst_qtconcurrenttask +QT = core testlib concurrent +SOURCES = tst_qtconcurrenttask.cpp diff --git a/tests/auto/concurrent/qtconcurrenttask/tst_qtconcurrenttask.cpp b/tests/auto/concurrent/qtconcurrenttask/tst_qtconcurrenttask.cpp new file mode 100644 index 00000000000..0d08efa463e --- /dev/null +++ b/tests/auto/concurrent/qtconcurrenttask/tst_qtconcurrenttask.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qtconcurrenttask.h> + +#include <QtTest/QtTest> + +class tst_QtConcurrentTask : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void taskWithFreeFunction(); + void taskWithClassMethod(); + void taskWithCallableObject(); + void taskWithLambda(); + void taskWithArguments(); + void useCustomThreadPool(); + void setPriority(); + void adjustAllSettings(); +}; + +using namespace QtConcurrent; + +void tst_QtConcurrentTask::taskWithFreeFunction() +{ + QVariant value(42); + + auto result = task(&qvariant_cast<int>) + .withArguments(value) + .spawn() + .result(); + + QCOMPARE(result, 42); +} +void tst_QtConcurrentTask::taskWithClassMethod() +{ + QString result("foobar"); + + task(&QString::chop).withArguments(&result, 3).spawn().waitForFinished(); + + QCOMPARE(result, "foo"); +} +void tst_QtConcurrentTask::taskWithCallableObject() +{ + QCOMPARE(task(std::plus<int>()) + .withArguments(40, 2) + .spawn() + .result(), + 42); +} + +void tst_QtConcurrentTask::taskWithLambda() +{ + QCOMPARE(task([]{ return 42; }).spawn().result(), 42); +} + +void tst_QtConcurrentTask::taskWithArguments() +{ + auto result = task([](int arg1, int arg2){ return arg1 + arg2; }) + .withArguments(40, 2) + .spawn() + .result(); + QCOMPARE(result, 42); +} + +void tst_QtConcurrentTask::useCustomThreadPool() +{ + QThreadPool pool; + + int result = 0; + task([&]{ result = 42; }).onThreadPool(pool).spawn().waitForFinished(); + + QCOMPARE(result, 42); +} + +void tst_QtConcurrentTask::setPriority() +{ + QThreadPool pool; + pool.setMaxThreadCount(1); + + QSemaphore sem; + + QVector<QFuture<void>> futureResults; + futureResults << task([&]{ sem.acquire(); }) + .onThreadPool(pool) + .spawn(); + + const int tasksCount = 10; + QVector<int> priorities(tasksCount); + std::iota(priorities.begin(), priorities.end(), 1); + auto seed = std::chrono::system_clock::now().time_since_epoch().count(); + std::shuffle(priorities.begin(), priorities.end(), std::default_random_engine(seed)); + + QVector<int> actual; + for (int priority : priorities) + futureResults << task([priority, &actual] { actual << priority; }) + .onThreadPool(pool) + .withPriority(priority) + .spawn(); + + sem.release(); + pool.waitForDone(); + + for (const auto &f : futureResults) + QVERIFY(f.isFinished()); + + QVector<int> expected(priorities); + std::sort(expected.begin(), expected.end(), std::greater<>()); + + QCOMPARE(actual, expected); +} + +void tst_QtConcurrentTask::adjustAllSettings() +{ + QThreadPool pool; + pool.setMaxThreadCount(1); + + const int priority = 10; + + QVector<int> result; + auto append = [&](auto &&...args){ (result << ... << args); }; + + task(std::move(append)) + .withArguments(1, 2, 3) + .onThreadPool(pool) + .withPriority(priority) + .spawn() + .waitForFinished(); + + QCOMPARE(result, QVector<int>({1, 2, 3})); +} + +QTEST_MAIN(tst_QtConcurrentTask) +#include "tst_qtconcurrenttask.moc" |