mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-11-24 04:20:46 +08:00
427 lines
9.9 KiB
C++
427 lines
9.9 KiB
C++
|
// Copyright (C) 2016 The Qt Company Ltd.
|
||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||
|
#include <qtconcurrentthreadengine.h>
|
||
|
#include <qexception.h>
|
||
|
#include <QThread>
|
||
|
#include <QElapsedTimer>
|
||
|
#include <QTest>
|
||
|
#include <QSet>
|
||
|
|
||
|
using namespace QtConcurrent;
|
||
|
|
||
|
class tst_QtConcurrentThreadEngine: public QObject
|
||
|
{
|
||
|
Q_OBJECT
|
||
|
private slots:
|
||
|
void runDirectly();
|
||
|
void result();
|
||
|
void runThroughStarter();
|
||
|
void cancel();
|
||
|
void throttle();
|
||
|
void threadCount();
|
||
|
void multipleResults();
|
||
|
void stresstest();
|
||
|
void cancelQueuedSlowUser();
|
||
|
#ifndef QT_NO_EXCEPTIONS
|
||
|
void exceptions();
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
|
||
|
class PrintUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
PrintUser() : ThreadEngine(QThreadPool::globalInstance()) {}
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
QTest::qSleep(50);
|
||
|
QTest::qSleep(100);
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::runDirectly()
|
||
|
{
|
||
|
PrintUser *engine = new PrintUser();
|
||
|
QFuture<void> f = engine->startAsynchronously();
|
||
|
f.waitForFinished();
|
||
|
}
|
||
|
|
||
|
class StringResultUser : public ThreadEngine<QString>
|
||
|
{
|
||
|
public:
|
||
|
typedef QString ResultType;
|
||
|
StringResultUser()
|
||
|
: ThreadEngine(QThreadPool::globalInstance())
|
||
|
, done(false) { }
|
||
|
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return !done;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
done = true;
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
|
||
|
QString *result() override
|
||
|
{
|
||
|
foo = "Foo";
|
||
|
return &foo;
|
||
|
}
|
||
|
QString foo;
|
||
|
bool done;
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::result()
|
||
|
{
|
||
|
// ThreadEngine will delete 'engine' when it finishes
|
||
|
auto engine = new StringResultUser();
|
||
|
auto future = engine->startAsynchronously();
|
||
|
QCOMPARE(future.result(), QString("Foo"));
|
||
|
}
|
||
|
|
||
|
class VoidResultUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
VoidResultUser() : ThreadEngine(QThreadPool::globalInstance()) {}
|
||
|
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return !done;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
done = true;
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
|
||
|
void *result() override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
bool done;
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::runThroughStarter()
|
||
|
{
|
||
|
ThreadEngineStarter<QString> starter = startThreadEngine(new StringResultUser());
|
||
|
QFuture<QString> f = starter.startAsynchronously();
|
||
|
QCOMPARE(f.result(), QString("Foo"));
|
||
|
}
|
||
|
|
||
|
class CancelUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
CancelUser() : ThreadEngine(QThreadPool::globalInstance()) {}
|
||
|
|
||
|
void *result() override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
while (this->isCanceled() == false)
|
||
|
{
|
||
|
QTest::qSleep(10);
|
||
|
}
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::cancel()
|
||
|
{
|
||
|
{
|
||
|
CancelUser *engine = new CancelUser();
|
||
|
QFuture<void> f = engine->startAsynchronously();
|
||
|
f.cancel();
|
||
|
f.waitForFinished();
|
||
|
}
|
||
|
{
|
||
|
CancelUser *engine = new CancelUser();
|
||
|
QFuture<void> f = engine->startAsynchronously();
|
||
|
QTest::qSleep(10);
|
||
|
f.cancel();
|
||
|
f.waitForFinished();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QAtomicInt count;
|
||
|
class ThrottleAlwaysUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
ThrottleAlwaysUser()
|
||
|
: ThreadEngine(QThreadPool::globalInstance())
|
||
|
{
|
||
|
count.storeRelaxed(initialCount = 100);
|
||
|
finishing = false;
|
||
|
}
|
||
|
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return !finishing;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
forever {
|
||
|
const int local = count.loadRelaxed();
|
||
|
if (local == 0) {
|
||
|
finishing = true;
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
|
||
|
if (count.testAndSetOrdered(local, local - 1))
|
||
|
break;
|
||
|
}
|
||
|
return ThrottleThread;
|
||
|
}
|
||
|
|
||
|
bool finishing;
|
||
|
int initialCount;
|
||
|
};
|
||
|
|
||
|
// Test that a user task with a thread function that always
|
||
|
// want to be throttled still completes. The thread engine
|
||
|
// should make keep one thread running at all times.
|
||
|
void tst_QtConcurrentThreadEngine::throttle()
|
||
|
{
|
||
|
const int repeats = 10;
|
||
|
for (int i = 0; i < repeats; ++i) {
|
||
|
QFuture<void> f = (new ThrottleAlwaysUser())->startAsynchronously();
|
||
|
f.waitForFinished();
|
||
|
QCOMPARE(count.loadRelaxed(), 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QSet<QThread *> threads;
|
||
|
QMutex mutex;
|
||
|
class ThreadCountUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
ThreadCountUser(bool finishImmediately = false)
|
||
|
: ThreadEngine(QThreadPool::globalInstance())
|
||
|
{
|
||
|
threads.clear();
|
||
|
finishing = finishImmediately;
|
||
|
}
|
||
|
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return !finishing;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
{
|
||
|
QMutexLocker lock(&mutex);
|
||
|
threads.insert(QThread::currentThread());
|
||
|
}
|
||
|
QTest::qSleep(10);
|
||
|
finishing = true;
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
|
||
|
bool finishing;
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::threadCount()
|
||
|
{
|
||
|
const int repeats = 10;
|
||
|
for (int i = 0; i < repeats; ++i) {
|
||
|
(new ThreadCountUser())->startAsynchronously().waitForFinished();
|
||
|
const auto count = threads.size();
|
||
|
const auto maxThreadCount = QThreadPool::globalInstance()->maxThreadCount();
|
||
|
QVERIFY(count <= maxThreadCount);
|
||
|
QVERIFY(!threads.contains(QThread::currentThread()));
|
||
|
}
|
||
|
|
||
|
// Set the finish flag immediately, this should give us one thread only.
|
||
|
for (int i = 0; i < repeats; ++i) {
|
||
|
(new ThreadCountUser(true /*finishImmediately*/))->startAsynchronously().waitForFinished();
|
||
|
const auto count = threads.size();
|
||
|
QCOMPARE(count, 1);
|
||
|
QVERIFY(!threads.contains(QThread::currentThread()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class MultipleResultsUser : public ThreadEngine<int>
|
||
|
{
|
||
|
public:
|
||
|
MultipleResultsUser() : ThreadEngine(QThreadPool::globalInstance()) {}
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
for (int i = 0; i < 10; ++i)
|
||
|
this->reportResult(&i);
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::multipleResults()
|
||
|
{
|
||
|
MultipleResultsUser *engine = new MultipleResultsUser();
|
||
|
QFuture<int> f = engine->startAsynchronously();
|
||
|
QCOMPARE(f.results().size() , 10);
|
||
|
QCOMPARE(f.resultAt(0), 0);
|
||
|
QCOMPARE(f.resultAt(5), 5);
|
||
|
QCOMPARE(f.resultAt(9), 9);
|
||
|
f.waitForFinished();
|
||
|
}
|
||
|
|
||
|
|
||
|
class NoThreadsUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
bool shouldStartThread() override
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
|
||
|
void *result() override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::stresstest()
|
||
|
{
|
||
|
const int times = 20000;
|
||
|
|
||
|
for (int i = 0; i < times; ++i) {
|
||
|
VoidResultUser *engine = new VoidResultUser();
|
||
|
engine->startAsynchronously().waitForFinished();
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < times; ++i) {
|
||
|
VoidResultUser *engine = new VoidResultUser();
|
||
|
engine->startAsynchronously();
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < times; ++i) {
|
||
|
VoidResultUser *engine = new VoidResultUser();
|
||
|
engine->startAsynchronously().waitForFinished();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const int sleepTime = 20;
|
||
|
class SlowUser : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
SlowUser() : ThreadEngine(QThreadPool::globalInstance()) {}
|
||
|
bool shouldStartThread() override { return false; }
|
||
|
ThreadFunctionResult threadFunction() override { QTest::qSleep(sleepTime); return ThreadFinished; }
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::cancelQueuedSlowUser()
|
||
|
{
|
||
|
const int times = 100;
|
||
|
|
||
|
QElapsedTimer t;
|
||
|
t.start();
|
||
|
|
||
|
{
|
||
|
QList<QFuture<void> > futures;
|
||
|
for (int i = 0; i < times; ++i) {
|
||
|
SlowUser *engine = new SlowUser();
|
||
|
futures.append(engine->startAsynchronously());
|
||
|
}
|
||
|
|
||
|
foreach(QFuture<void> future, futures)
|
||
|
future.cancel();
|
||
|
}
|
||
|
|
||
|
QVERIFY(t.elapsed() < (sleepTime * times) / 2);
|
||
|
}
|
||
|
|
||
|
#ifndef QT_NO_EXCEPTIONS
|
||
|
|
||
|
class QtConcurrentExceptionThrower : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
QtConcurrentExceptionThrower(QThread *blockThread = nullptr)
|
||
|
: ThreadEngine(QThreadPool::globalInstance())
|
||
|
{
|
||
|
this->blockThread = blockThread;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
QTest::qSleep(50);
|
||
|
throw QException();
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
QThread *blockThread;
|
||
|
};
|
||
|
|
||
|
class IntExceptionThrower : public ThreadEngine<void>
|
||
|
{
|
||
|
public:
|
||
|
IntExceptionThrower(QThread *blockThread = nullptr)
|
||
|
: ThreadEngine(QThreadPool::globalInstance())
|
||
|
{
|
||
|
this->blockThread = blockThread;
|
||
|
}
|
||
|
|
||
|
ThreadFunctionResult threadFunction() override
|
||
|
{
|
||
|
QTest::qSleep(50);
|
||
|
throw int();
|
||
|
return ThreadFinished;
|
||
|
}
|
||
|
QThread *blockThread;
|
||
|
};
|
||
|
|
||
|
void tst_QtConcurrentThreadEngine::exceptions()
|
||
|
{
|
||
|
{
|
||
|
bool caught = false;
|
||
|
try {
|
||
|
QtConcurrentExceptionThrower *e = new QtConcurrentExceptionThrower();
|
||
|
QFuture<void> f = e->startAsynchronously();
|
||
|
f.waitForFinished();
|
||
|
} catch (const QException &) {
|
||
|
caught = true;
|
||
|
}
|
||
|
QVERIFY2(caught, "did not get exception");
|
||
|
}
|
||
|
|
||
|
{
|
||
|
bool caught = false;
|
||
|
try {
|
||
|
IntExceptionThrower *e = new IntExceptionThrower();
|
||
|
QFuture<void> f = e->startAsynchronously();
|
||
|
f.waitForFinished();
|
||
|
} catch (const QUnhandledException &ex) {
|
||
|
// Make sure the exception info is not lost
|
||
|
try {
|
||
|
if (ex.exception())
|
||
|
std::rethrow_exception(ex.exception());
|
||
|
} catch (int) {
|
||
|
caught = true;
|
||
|
}
|
||
|
}
|
||
|
QVERIFY2(caught, "did not get exception");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
QTEST_MAIN(tst_QtConcurrentThreadEngine)
|
||
|
|
||
|
#include "tst_qtconcurrentthreadengine.moc"
|