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>
This commit is contained in:
Vitaly Fanaskov 2020-03-11 18:07:03 +01:00
parent d975ad4ed7
commit 5a0d4f3313
16 changed files with 808 additions and 12 deletions

View File

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

View File

@ -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 += \

View File

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

View File

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

View File

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

View File

@ -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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,3 +6,4 @@ add_subdirectory(qtconcurrentmap)
add_subdirectory(qtconcurrentmedian)
add_subdirectory(qtconcurrentrun)
add_subdirectory(qtconcurrentthreadengine)
add_subdirectory(qtconcurrenttask)

View File

@ -5,5 +5,6 @@ SUBDIRS=\
qtconcurrentmap \
qtconcurrentmedian \
qtconcurrentrun \
qtconcurrentthreadengine
qtconcurrentthreadengine \
qtconcurrenttask

View File

@ -0,0 +1,6 @@
add_qt_test(tst_qtconcurrenttask
SOURCES
tst_qtconcurrenttask.cpp
PUBLIC_LIBRARIES
Qt::Concurrent
)

View File

@ -0,0 +1,4 @@
CONFIG += testcase
TARGET = tst_qtconcurrenttask
QT = core testlib concurrent
SOURCES = tst_qtconcurrenttask.cpp

View File

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