From 349b49caaa4e36c4e4f200d54b75a4be9cb80940 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 26 Jan 2016 16:48:21 +0100 Subject: [PATCH] RunExtensions: Allow running functions on arbitrary thread pools Change-Id: Icf642e7365535e4b2ca6fa4dee90a7033eb39af7 Reviewed-by: Erik Verbruggen --- src/libs/utils/runextensions.h | 91 ++++++++++++++++++- .../auto/runextensions/tst_runextensions.cpp | 87 ++++++++++++++++++ 2 files changed, 173 insertions(+), 5 deletions(-) diff --git a/src/libs/utils/runextensions.h b/src/libs/utils/runextensions.h index 0bb55656a22..7427dbfb130 100644 --- a/src/libs/utils/runextensions.h +++ b/src/libs/utils/runextensions.h @@ -28,10 +28,10 @@ #include "qtcassert.h" -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -540,6 +540,73 @@ runAsyncImpl(QFutureInterface futureInterface, const Function &funct futureInterface.reportFinished(); } +// can be replaced with std::(make_)index_sequence with C++14 +template +struct indexSequence { }; +template +struct makeIndexSequence : makeIndexSequence { }; +template +struct makeIndexSequence<0, S...> { typedef indexSequence type; }; + +template +typename std::decay::type +decayCopy(T&& v) +{ + return std::forward(v); +} + +template +class AsyncJob : public QRunnable +{ +public: + AsyncJob(Function &&function, Args&&... args) + // decay copy like std::thread + : data(decayCopy(std::forward(function)), decayCopy(std::forward(args))...) + { + // we need to report it as started even though it isn't yet, because someone might + // call waitForFinished on the future, which does _not_ block if the future is not started + futureInterface.setRunnable(this); + futureInterface.reportStarted(); + } + + ~AsyncJob() + { + // QThreadPool can delete runnables even if they were never run (e.g. QThreadPool::clear). + // Since we reported them as started, we make sure that we always report them as finished. + // reportFinished only actually sends the signal if it wasn't already finished. + futureInterface.reportFinished(); + } + + QFuture future() { return futureInterface.future(); } + + void run() override + { + if (futureInterface.isCanceled()) { + futureInterface.reportFinished(); + return; + } + runHelper(typename makeIndexSequence::value>::type()); + } + + void setThreadPool(QThreadPool *pool) + { + futureInterface.setThreadPool(pool); + } + +private: + using Data = std::tuple::type, typename std::decay::type...>; + + template + void runHelper(indexSequence) + { + // invalidates data, which is moved into the call + runAsyncImpl(futureInterface, std::move(std::get(data))...); + } + + Data data; + QFutureInterface futureInterface; +}; + } // Internal template mapReduce(const Container &container, const InitFunction & \sa std::thread \sa std::invoke */ -template +template ::type, QThreadPool>::value + >::type> QFuture runAsync(Function &&function, Args&&... args) { QFutureInterface futureInterface; @@ -591,6 +661,17 @@ QFuture runAsync(Function &&function, Args&&... args) return futureInterface.future(); } +template +QFuture runAsync(QThreadPool *pool, Function &&function, Args&&... args) +{ + auto job = new Internal::AsyncJob + (std::forward(function), std::forward(args)...); + job->setThreadPool(pool); + QFuture future = job->future(); + pool->start(job); + return future; +} + } // Utils #endif // RUNEXTENSIONS_H diff --git a/tests/auto/runextensions/tst_runextensions.cpp b/tests/auto/runextensions/tst_runextensions.cpp index fb18d3a398c..9237d8f5ac6 100644 --- a/tests/auto/runextensions/tst_runextensions.cpp +++ b/tests/auto/runextensions/tst_runextensions.cpp @@ -33,6 +33,7 @@ class tst_RunExtensions : public QObject private slots: void runAsync(); + void runInThreadPool(); }; void report3(QFutureInterface &fi) @@ -181,6 +182,92 @@ void tst_RunExtensions::runAsync() QList({QString(QLatin1String("rvalue"))})); } +void tst_RunExtensions::runInThreadPool() +{ + QScopedPointer pool(new QThreadPool); + // free function pointer + QCOMPARE(Utils::runAsync(pool.data(), &report3).results(), + QList({0, 2, 1})); + QCOMPARE(Utils::runAsync(pool.data(), report3).results(), + QList({0, 2, 1})); + + QCOMPARE(Utils::runAsync(pool.data(), reportN, 4).results(), + QList({0, 0, 0, 0})); + QCOMPARE(Utils::runAsync(pool.data(), reportN, 2).results(), + QList({0, 0})); + + QString s = QLatin1String("string"); + const QString &crs = QLatin1String("cr string"); + const QString cs = QLatin1String("c string"); + + QCOMPARE(Utils::runAsync(pool.data(), reportString1, s).results(), + QList({s})); + QCOMPARE(Utils::runAsync(pool.data(), reportString1, crs).results(), + QList({crs})); + QCOMPARE(Utils::runAsync(pool.data(), reportString1, cs).results(), + QList({cs})); + QCOMPARE(Utils::runAsync(pool.data(), reportString1, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + + QCOMPARE(Utils::runAsync(pool.data(), reportString2, s).results(), + QList({s})); + QCOMPARE(Utils::runAsync(pool.data(), reportString2, crs).results(), + QList({crs})); + QCOMPARE(Utils::runAsync(pool.data(), reportString2, cs).results(), + QList({cs})); + QCOMPARE(Utils::runAsync(pool.data(), reportString2, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + + // lambda + QCOMPARE(Utils::runAsync(pool.data(), [](QFutureInterface &fi, int n) { + fi.reportResults(QVector(n, 0)); + }, 3).results(), + QList({0, 0, 0})); + + // std::function + const std::function&,int)> fun = [](QFutureInterface &fi, int n) { + fi.reportResults(QVector(n, 0)); + }; + QCOMPARE(Utils::runAsync(pool.data(), fun, 2).results(), + QList({0, 0})); + + // operator() + QCOMPARE(Utils::runAsync(pool.data(), Callable(), 3).results(), + QList({0, 0, 0})); + const Callable c{}; + QCOMPARE(Utils::runAsync(pool.data(), c, 2).results(), + QList({0, 0})); + + // static member functions + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::staticMember0).results(), + QList({0, 2, 1})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::staticMember1, 2).results(), + QList({0, 0})); + + // member functions + const MyObject obj{}; + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::member0, &obj).results(), + QList({0, 2, 1})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::member1, &obj, 4).results(), + QList({0, 0, 0, 0})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, s).results(), + QList({s})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, crs).results(), + QList({crs})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, cs).results(), + QList({cs})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, s).results(), + QList({s})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, crs).results(), + QList({crs})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, cs).results(), + QList({cs})); + QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); +} + QTEST_MAIN(tst_RunExtensions) #include "tst_runextensions.moc"