diff options
author | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2017-04-03 13:52:20 +0100 |
---|---|---|
committer | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2017-04-24 15:24:22 +0000 |
commit | 597d4ff7962c0add87e4b93da4c366503d11aff5 (patch) | |
tree | 7d90570c83e914efab1ff9adf40e505e2c9611a6 /tests/auto | |
parent | 711e94916ad4b97128849072caf977c13152c64c (diff) |
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>
Diffstat (limited to 'tests/auto')
-rw-r--r-- | tests/auto/corelib/thread/qthread/qthread.pro | 2 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qthread/tst_qthread.cpp | 259 |
2 files changed, 261 insertions, 0 deletions
diff --git a/tests/auto/corelib/thread/qthread/qthread.pro b/tests/auto/corelib/thread/qthread/qthread.pro index 18d867ecef..381f6c9e45 100644 --- a/tests/auto/corelib/thread/qthread/qthread.pro +++ b/tests/auto/corelib/thread/qthread/qthread.pro @@ -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 diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 0efbc5d01e..b107cf4b60 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -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 |