QThread: add static create function
In the spirit of std::thread, which takes a function to call and its parameters, and runs it in a new thread. Since the user might want to connect to signals, move QObjects into the new thread, etc., the new thread is not immediately started. Although technically all of this _should_ be implementable in pure C++11, there is nothing in the Standard to help us not reinvent all the plumbing: packing the decay'd parameters, storing them, invoking the function over the parameters (honoring INVOKE/std::invoke semantics). std::function does not do the job, as it's copiable and therefore does not support move-only functors; std::bind does not have INVOKE semantics. I certainly do not want to reimplement all the required facilities inside of Qt. Therefore, the full blown implementation requires C++17 (std::invoke). In order to make this useful also in pre-C++17, there are two additional implementations (C++11 and C++14) that support just a callable, without any arguments passed to it. The C++11 implementation makes use of a class to store and call the callable (even move-only ones); basically, it's what a closure type for a C++14 lambda would look like. An alternative implementation could've used some of the existing facilities inside QObject::connect implementation that store a functor (for the connect() overload connecting to free functions), namely: the QtPrivate::QFunctorSlotObject class. However: * QFunctorSlotObject does not support move-only callables (see QTBUG-60339); * QFunctorSlotObject itself is not a callable (apparently by design), and requires to be wrapped in a lambda that calls call() on it; * the moment QTBUG-60339 is solved, we'd need the same handwritten closure to keep QFunctorSlotObject working with move-only callabes. So: just use the handwritten one. The C++14 implementation is a simplified version of the C++11 one, actually using a generalized lambda capture (corresponding to the handwritten C++11 closure type). All three implementations use std::async (with a deferred launch policy, a nice use case for it!) under the hood. It's certainly an overkill for our use case, as we don't need the std::future, but at least std::async does all the plumbing for us. [ChangeLog][QtCore][QThread] Added the QThread::create function. Change-Id: I339d0be6f689df7d56766839baebda0aa2f7e94c Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
711e94916a
commit
597d4ff796
@ -880,6 +880,51 @@ bool QThread::isInterruptionRequested() const
|
||||
return d->interruptionRequested;
|
||||
}
|
||||
|
||||
/*
|
||||
\fn template <typename Function, typename Args...> static QThread *QThread::create(Function &&f, Args &&... args)
|
||||
\since 5.10
|
||||
|
||||
Creates a new QThread object that will execute the function \a f with the
|
||||
arguments \a args.
|
||||
|
||||
The new thread is not started -- it must be started by an explicit call
|
||||
to start(). This allows you to connect to its signals, move QObjects
|
||||
to the thread, choose the new thread's priority and so on. The function
|
||||
\a f will be called in the new thread.
|
||||
|
||||
Returns the newly created QThread instance.
|
||||
|
||||
\note the caller acquires ownership of the returned QThread instance.
|
||||
|
||||
\note this function is only available when using C++17.
|
||||
|
||||
\warning do not call start() on the returned QThread instance more than once;
|
||||
doing so will result in undefined behavior.
|
||||
|
||||
\sa start()
|
||||
*/
|
||||
|
||||
/*
|
||||
\fn template <typename Function> static QThread *QThread::create(Function &&f)
|
||||
\since 5.10
|
||||
|
||||
Creates a new QThread object that will execute the function \a f.
|
||||
|
||||
The new thread is not started -- it must be started by an explicit call
|
||||
to start(). This allows you to connect to its signals, move QObjects
|
||||
to the thread, choose the new thread's priority and so on. The function
|
||||
\a f will be called in the new thread.
|
||||
|
||||
Returns the newly created QThread instance.
|
||||
|
||||
\note the caller acquires ownership of the returned QThread instance.
|
||||
|
||||
\warning do not call start() on the returned QThread instance more than once;
|
||||
doing so will result in undefined behavior.
|
||||
|
||||
\sa start()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QDaemonThread
|
||||
\since 5.5
|
||||
|
@ -42,6 +42,23 @@
|
||||
|
||||
#include <QtCore/qobject.h>
|
||||
|
||||
// The implementation of QThread::create uses various C++14/C++17 facilities;
|
||||
// we must check for their presence. For std::async (used in all codepaths)
|
||||
// there is no SG10 feature macro; just test for the header presence.
|
||||
// For the C++17 codepath do some more throughout checks for std::invoke and
|
||||
// C++14 lambdas availability.
|
||||
#if QT_HAS_INCLUDE(<future>)
|
||||
# define QTHREAD_HAS_CREATE
|
||||
# include <future> // for std::async
|
||||
# include <functional> // for std::invoke; no guard needed as it's a C++98 header
|
||||
|
||||
# if defined(__cpp_lib_invoke) && __cpp_lib_invoke >= 201411 \
|
||||
&& defined(__cpp_init_captures) && __cpp_init_captures >= 201304 \
|
||||
&& defined(__cpp_generic_lambdas) && __cpp_generic_lambdas >= 201304
|
||||
# define QTHREAD_HAS_VARIADIC_CREATE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -98,6 +115,16 @@ public:
|
||||
bool event(QEvent *event) Q_DECL_OVERRIDE;
|
||||
int loopLevel() const;
|
||||
|
||||
#ifdef QTHREAD_HAS_CREATE
|
||||
#ifdef QTHREAD_HAS_VARIADIC_CREATE
|
||||
template <typename Function, typename... Args>
|
||||
static QThread *create(Function &&f, Args &&... args);
|
||||
#else
|
||||
template <typename Function>
|
||||
static QThread *create(Function &&f);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public Q_SLOTS:
|
||||
void start(Priority = InheritPriority);
|
||||
void terminate();
|
||||
@ -131,6 +158,99 @@ private:
|
||||
friend class QThreadData;
|
||||
};
|
||||
|
||||
#ifdef QTHREAD_HAS_CREATE
|
||||
namespace QtPrivate {
|
||||
|
||||
class QThreadCreateThread : public QThread
|
||||
{
|
||||
public:
|
||||
#if defined(QTHREAD_HAS_VARIADIC_CREATE)
|
||||
// C++17: std::thread's constructor complying call
|
||||
template <typename Function, typename... Args>
|
||||
explicit QThreadCreateThread(Function &&f, Args &&... args)
|
||||
: m_future(std::async(std::launch::deferred,
|
||||
[f = static_cast<typename std::decay<Function>::type>(std::forward<Function>(f))](auto &&... args) mutable -> void
|
||||
{
|
||||
(void)std::invoke(std::move(f), std::forward<decltype(args)>(args)...);
|
||||
}, std::forward<Args>(args)...))
|
||||
{
|
||||
}
|
||||
#elif defined(__cpp_init_captures) && __cpp_init_captures >= 201304
|
||||
// C++14: implementation for just one callable
|
||||
template <typename Function>
|
||||
explicit QThreadCreateThread(Function &&f)
|
||||
: m_future(std::async(std::launch::deferred,
|
||||
[f = static_cast<typename std::decay<Function>::type>(std::forward<Function>(f))]() mutable -> void
|
||||
{
|
||||
(void)f();
|
||||
}))
|
||||
{
|
||||
}
|
||||
#else
|
||||
private:
|
||||
// C++11: same as C++14, but with a workaround for not having generalized lambda captures
|
||||
template <typename Function>
|
||||
struct Callable
|
||||
{
|
||||
explicit Callable(Function &&f)
|
||||
: m_function(std::forward<Function>(f))
|
||||
{
|
||||
}
|
||||
|
||||
#if defined(Q_COMPILER_DEFAULT_MEMBERS) && defined(Q_COMPILER_DELETE_MEMBERS)
|
||||
// Apply the same semantics of a lambda closure type w.r.t. the special
|
||||
// member functions, if possible: delete the copy assignment operator,
|
||||
// bring back all the others as per the RO5 (cf. §8.1.5.1/11 [expr.prim.lambda.closure])
|
||||
~Callable() = default;
|
||||
Callable(const Callable &) = default;
|
||||
Callable(Callable &&) = default;
|
||||
Callable &operator=(const Callable &) = delete;
|
||||
Callable &operator=(Callable &&) = default;
|
||||
#endif
|
||||
|
||||
void operator()()
|
||||
{
|
||||
(void)m_function();
|
||||
}
|
||||
|
||||
typename std::decay<Function>::type m_function;
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename Function>
|
||||
explicit QThreadCreateThread(Function &&f)
|
||||
: m_future(std::async(std::launch::deferred, Callable<Function>(std::forward<Function>(f))))
|
||||
{
|
||||
}
|
||||
#endif // QTHREAD_HAS_VARIADIC_CREATE
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
m_future.get();
|
||||
}
|
||||
|
||||
std::future<void> m_future;
|
||||
};
|
||||
|
||||
} // namespace QtPrivate
|
||||
|
||||
#ifdef QTHREAD_HAS_VARIADIC_CREATE
|
||||
template <typename Function, typename... Args>
|
||||
QThread *QThread::create(Function &&f, Args &&... args)
|
||||
{
|
||||
return new QtPrivate::QThreadCreateThread(std::forward<Function>(f), std::forward<Args>(args)...);
|
||||
}
|
||||
#else
|
||||
template <typename Function>
|
||||
QThread *QThread::create(Function &&f)
|
||||
{
|
||||
return new QtPrivate::QThreadCreateThread(std::forward<Function>(f));
|
||||
}
|
||||
#endif // QTHREAD_HAS_VARIADIC_CREATE
|
||||
|
||||
#endif // QTHREAD_HAS_CREATE
|
||||
|
||||
#else // QT_NO_THREAD
|
||||
|
||||
class Q_CORE_EXPORT QThread : public QObject
|
||||
|
@ -2,3 +2,5 @@ CONFIG += testcase
|
||||
TARGET = tst_qthread
|
||||
QT = core testlib
|
||||
SOURCES = tst_qthread.cpp
|
||||
qtConfig(c++14):CONFIG += c++14
|
||||
qtConfig(c++1z):CONFIG += c++1z
|
||||
|
@ -47,6 +47,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
#include <exception>
|
||||
#endif
|
||||
|
||||
class tst_QThread : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -98,6 +102,8 @@ private slots:
|
||||
void stressTest();
|
||||
|
||||
void quitLock();
|
||||
|
||||
void create();
|
||||
};
|
||||
|
||||
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
|
||||
@ -1325,6 +1331,259 @@ void tst_QThread::quitLock()
|
||||
QVERIFY(exitThreadCalled);
|
||||
}
|
||||
|
||||
void tst_QThread::create()
|
||||
{
|
||||
#ifndef QTHREAD_HAS_CREATE
|
||||
QSKIP("This test requires QThread::create");
|
||||
#else
|
||||
{
|
||||
const auto &function = [](){};
|
||||
QScopedPointer<QThread> thread(QThread::create(function));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
}
|
||||
|
||||
{
|
||||
// no side effects before starting
|
||||
int i = 0;
|
||||
const auto &function = [&i]() { i = 42; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
QCOMPARE(i, 0);
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 42);
|
||||
}
|
||||
|
||||
{
|
||||
// control thread progress
|
||||
QSemaphore semaphore1;
|
||||
QSemaphore semaphore2;
|
||||
|
||||
const auto &function = [&semaphore1, &semaphore2]() -> void
|
||||
{
|
||||
semaphore1.acquire();
|
||||
semaphore2.release();
|
||||
};
|
||||
|
||||
QScopedPointer<QThread> thread(QThread::create(function));
|
||||
|
||||
QVERIFY(thread);
|
||||
thread->start();
|
||||
QTRY_VERIFY(thread->isRunning());
|
||||
semaphore1.release();
|
||||
semaphore2.acquire();
|
||||
QVERIFY(thread->wait());
|
||||
QVERIFY(!thread->isRunning());
|
||||
}
|
||||
|
||||
{
|
||||
// ignore return values
|
||||
const auto &function = []() { return 42; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
}
|
||||
|
||||
{
|
||||
// return value of create
|
||||
QScopedPointer<QThread> thread;
|
||||
QSemaphore s;
|
||||
const auto &function = [&thread, &s]() -> void
|
||||
{
|
||||
s.acquire();
|
||||
QCOMPARE(thread.data(), QThread::currentThread());
|
||||
};
|
||||
|
||||
thread.reset(QThread::create(function));
|
||||
QVERIFY(thread);
|
||||
thread->start();
|
||||
QTRY_VERIFY(thread->isRunning());
|
||||
s.release();
|
||||
QVERIFY(thread->wait());
|
||||
}
|
||||
|
||||
{
|
||||
// move-only parameters
|
||||
struct MoveOnlyValue {
|
||||
explicit MoveOnlyValue(int v) : v(v) {}
|
||||
~MoveOnlyValue() = default;
|
||||
MoveOnlyValue(const MoveOnlyValue &) = delete;
|
||||
MoveOnlyValue(MoveOnlyValue &&) = default;
|
||||
MoveOnlyValue &operator=(const MoveOnlyValue &) = delete;
|
||||
MoveOnlyValue &operator=(MoveOnlyValue &&) = default;
|
||||
int v;
|
||||
};
|
||||
|
||||
struct MoveOnlyFunctor {
|
||||
explicit MoveOnlyFunctor(int *i) : i(i) {}
|
||||
~MoveOnlyFunctor() = default;
|
||||
MoveOnlyFunctor(const MoveOnlyFunctor &) = delete;
|
||||
MoveOnlyFunctor(MoveOnlyFunctor &&) = default;
|
||||
MoveOnlyFunctor &operator=(const MoveOnlyFunctor &) = delete;
|
||||
MoveOnlyFunctor &operator=(MoveOnlyFunctor &&) = default;
|
||||
int operator()() { return (*i = 42); }
|
||||
int *i;
|
||||
};
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
MoveOnlyFunctor f(&i);
|
||||
QScopedPointer<QThread> thread(QThread::create(std::move(f)));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 42);
|
||||
}
|
||||
|
||||
#if defined(__cpp_init_captures) && __cpp_init_captures >= 201304
|
||||
{
|
||||
int i = 0;
|
||||
MoveOnlyValue mo(123);
|
||||
auto moveOnlyFunction = [&i, mo = std::move(mo)]() { i = mo.v; };
|
||||
QScopedPointer<QThread> thread(QThread::create(std::move(moveOnlyFunction)));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 123);
|
||||
}
|
||||
#endif // __cpp_init_captures
|
||||
|
||||
#ifdef QTHREAD_HAS_VARIADIC_CREATE
|
||||
{
|
||||
int i = 0;
|
||||
const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function, MoveOnlyValue(123)));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 123);
|
||||
}
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
const auto &function = [&i](MoveOnlyValue &&mo) { i = mo.v; };
|
||||
MoveOnlyValue mo(-1);
|
||||
QScopedPointer<QThread> thread(QThread::create(function, std::move(mo)));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, -1);
|
||||
}
|
||||
#endif // QTHREAD_HAS_VARIADIC_CREATE
|
||||
}
|
||||
|
||||
#ifdef QTHREAD_HAS_VARIADIC_CREATE
|
||||
{
|
||||
// simple parameter passing
|
||||
int i = 0;
|
||||
const auto &function = [&i](int j, int k) { i = j * k; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function, 3, 4));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
QCOMPARE(i, 0);
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 12);
|
||||
}
|
||||
|
||||
{
|
||||
// ignore return values (with parameters)
|
||||
const auto &function = [](double d) { return d * 2.0; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function, 3.14));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
}
|
||||
|
||||
{
|
||||
// handling of pointers to member functions, std::ref, etc.
|
||||
struct S {
|
||||
S() : v(0) {}
|
||||
void doSomething() { ++v; }
|
||||
int v;
|
||||
};
|
||||
|
||||
S object;
|
||||
|
||||
QCOMPARE(object.v, 0);
|
||||
|
||||
QScopedPointer<QThread> thread;
|
||||
thread.reset(QThread::create(&S::doSomething, object));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
|
||||
QCOMPARE(object.v, 0); // a copy was passed, this should still be 0
|
||||
|
||||
thread.reset(QThread::create(&S::doSomething, std::ref(object)));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
|
||||
QCOMPARE(object.v, 1);
|
||||
|
||||
thread.reset(QThread::create(&S::doSomething, &object));
|
||||
QVERIFY(thread);
|
||||
QVERIFY(!thread->isRunning());
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
|
||||
QCOMPARE(object.v, 2);
|
||||
}
|
||||
|
||||
{
|
||||
// std::ref into ordinary reference
|
||||
int i = 42;
|
||||
const auto &function = [](int &i) { i *= 2; };
|
||||
QScopedPointer<QThread> thread(QThread::create(function, std::ref(i)));
|
||||
QVERIFY(thread);
|
||||
thread->start();
|
||||
QVERIFY(thread->wait());
|
||||
QCOMPARE(i, 84);
|
||||
}
|
||||
|
||||
#ifndef QT_NO_EXCEPTIONS
|
||||
{
|
||||
// exceptions when copying/decaying the arguments are thrown at build side and won't terminate
|
||||
class ThreadException : public std::exception
|
||||
{
|
||||
};
|
||||
|
||||
struct ThrowWhenCopying
|
||||
{
|
||||
ThrowWhenCopying() = default;
|
||||
ThrowWhenCopying(const ThrowWhenCopying &)
|
||||
{
|
||||
throw ThreadException();
|
||||
}
|
||||
~ThrowWhenCopying() = default;
|
||||
ThrowWhenCopying &operator=(const ThrowWhenCopying &) = default;
|
||||
};
|
||||
|
||||
const auto &function = [](const ThrowWhenCopying &){};
|
||||
QScopedPointer<QThread> thread;
|
||||
ThrowWhenCopying t;
|
||||
QVERIFY_EXCEPTION_THROWN(thread.reset(QThread::create(function, t)), ThreadException);
|
||||
QVERIFY(!thread);
|
||||
}
|
||||
#endif // QT_NO_EXCEPTIONS
|
||||
#endif // QTHREAD_HAS_VARIADIC_CREATE
|
||||
#endif // QTHREAD_HAS_CREATE
|
||||
}
|
||||
|
||||
class StopableJob : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
Loading…
x
Reference in New Issue
Block a user