Add new ConcurrentRun templates for void functions, and functions with 3 arguments + corresponding test cases.

+ SongLoader now has its own QThreadPool to load folders/playlist in background.
Update issue 2598
This should fix slowliness problems reported.
This commit is contained in:
Arnaud Bienner 2012-07-16 00:06:55 +02:00
parent f1117231e2
commit 0e3dc5a8be
7 changed files with 331 additions and 34 deletions

View File

@ -1,11 +0,0 @@
#include "concurrentrun.h"
namespace ConcurrentRun {
void Run(
QThreadPool* threadpool,
std::tr1::function<void ()> function) {
(new ThreadFunctorVoid(function))->Start(threadpool);
}
} // namespace ConcurrentRun

View File

@ -20,6 +20,9 @@
#include <tr1/functional>
#include <boost/type_traits.hpp>
#include <boost/utility.hpp>
#include <QFuture>
#include <QRunnable>
#include <QThreadPool>
@ -40,11 +43,18 @@
Run() functions are used for convenience: to directly create a new
ThreadFunctor object and start it.
Currently, only functions with one or two arguments and a return value are
Currently, only functions with zero, one, two or three arguments are
supported, but other might be added easily for X arguments by defining a new
ThreadFunctorBasX class, and add new Run() function.
ThreadFunctorX class (and eventually a second class for handling functions
which return void: see existing classes for exampels), and add new Run()
function.
*/
/*
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
non-void result):
*/
template<typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
public:
@ -62,6 +72,15 @@ class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable
}
virtual void run() = 0;
};
// Base implemenation for functions having a result to be returned
template<typename ReturnType, class Enabled = void>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
public:
ThreadFunctor() {}
virtual void run() = 0;
void End() {
this->reportResult(result_);
@ -72,24 +91,64 @@ class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable
ReturnType result_;
};
// Use bool as a placeholder result.
class ThreadFunctorVoid : public ThreadFunctorBase<bool> {
// Base implementation for functions with void result
template<typename ReturnType>
class ThreadFunctor<ReturnType, typename boost::enable_if<boost::is_void<ReturnType> >::type>
: public ThreadFunctorBase<ReturnType> {
public:
ThreadFunctorVoid(std::tr1::function<void ()> function)
ThreadFunctor() {}
virtual void run() = 0;
void End() {
this->reportFinished();
}
};
/*
ThreadFunctor with no arguments:
*/
// Non-void result
template<typename ReturnType, class Enabled = void>
class ThreadFunctor0 : public ThreadFunctor<ReturnType> {
public:
ThreadFunctor0(std::tr1::function<ReturnType ()> function)
: function_(function)
{ }
void run() {
this->result_ = function_();
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<ReturnType ()> function_;
};
// Void result
template<typename ReturnType>
class ThreadFunctor0<ReturnType, typename boost::enable_if<boost::is_void<ReturnType> >::type>
: public ThreadFunctor<ReturnType> {
public:
ThreadFunctor0(std::tr1::function<ReturnType ()> function)
: function_(function)
{ }
void run() {
function_();
ThreadFunctorBase<bool>::End();
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<void ()> function_;
private:
std::tr1::function<ReturnType ()> function_;
};
template<typename ReturnType, typename Arg>
class ThreadFunctor1 : public ThreadFunctorBase<ReturnType> {
/*
ThreadFunctor with one argument:
*/
// Non-void result
template<typename ReturnType, typename Arg, class Enabled = void>
class ThreadFunctor1 : public ThreadFunctor<ReturnType> {
public:
ThreadFunctor1(std::tr1::function<ReturnType (Arg)> function,
const Arg& arg)
@ -99,7 +158,7 @@ public:
void run() {
this->result_ = function_(arg_);
ThreadFunctorBase<ReturnType>::End();
ThreadFunctor<ReturnType>::End();
}
private:
@ -107,8 +166,33 @@ private:
Arg arg_;
};
template<typename ReturnType, typename Arg1, typename Arg2>
class ThreadFunctor2 : public ThreadFunctorBase<ReturnType> {
// Void result
template<typename ReturnType, typename Arg>
class ThreadFunctor1<ReturnType, Arg, typename boost::enable_if<boost::is_void<ReturnType> >::type>
: public ThreadFunctor<ReturnType> {
public:
ThreadFunctor1(std::tr1::function<ReturnType (Arg)> function,
const Arg& arg)
: function_(function),
arg_(arg)
{ }
void run() {
function_(arg_);
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<ReturnType (Arg)> function_;
Arg arg_;
};
/*
ThreadFunctor with two arguments:
*/
// Non-void result
template<typename ReturnType, typename Arg1, typename Arg2, class Enabled = void>
class ThreadFunctor2 : public ThreadFunctor<ReturnType> {
public:
ThreadFunctor2(std::tr1::function<ReturnType (Arg1, Arg2)> function,
const Arg1& arg1, const Arg2& arg2)
@ -119,7 +203,7 @@ public:
void run() {
this->result_ = function_(arg1_, arg2_);
ThreadFunctorBase<ReturnType>::End();
ThreadFunctor<ReturnType>::End();
}
private:
@ -128,10 +212,94 @@ private:
Arg2 arg2_;
};
// Void result
template<typename ReturnType, typename Arg1, typename Arg2>
class ThreadFunctor2<ReturnType, Arg1, Arg2, typename boost::enable_if<boost::is_void<ReturnType> >::type>
: public ThreadFunctor<ReturnType> {
public:
ThreadFunctor2(std::tr1::function<ReturnType (Arg1, Arg2)> function,
const Arg1& arg1, const Arg2& arg2)
: function_(function),
arg1_(arg1),
arg2_(arg2)
{ }
void run() {
function_(arg1_, arg2_);
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<ReturnType (Arg1, Arg2)> function_;
Arg1 arg1_;
Arg2 arg2_;
};
/*
ThreadFunctor with three arguments:
*/
// Non-void result
template<typename ReturnType, typename Arg1, typename Arg2, typename Arg3, class Enabled = void>
class ThreadFunctor3 : public ThreadFunctor<ReturnType> {
public:
ThreadFunctor3(std::tr1::function<ReturnType (Arg1, Arg2, Arg3)> function,
const Arg1& arg1, const Arg2& arg2, const Arg3& arg3)
: function_(function),
arg1_(arg1),
arg2_(arg2),
arg3_(arg3)
{ }
void run() {
this->result_ = function_(arg1_, arg2_, arg3_);
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<ReturnType (Arg1, Arg2, Arg3)> function_;
Arg1 arg1_;
Arg2 arg2_;
Arg3 arg3_;
};
// Void result
template<typename ReturnType, typename Arg1, typename Arg2, typename Arg3>
class ThreadFunctor3<ReturnType, Arg1, Arg2, Arg3, typename boost::enable_if<boost::is_void<ReturnType> >::type>
: public ThreadFunctor<ReturnType> {
public:
ThreadFunctor3(std::tr1::function<ReturnType (Arg1, Arg2, Arg3)> function,
const Arg1& arg1, const Arg2& arg2, const Arg3& arg3)
: function_(function),
arg1_(arg1),
arg2_(arg2),
arg3_(arg3)
{ }
void run() {
function_(arg1_, arg2_, arg3_);
ThreadFunctor<ReturnType>::End();
}
private:
std::tr1::function<ReturnType (Arg1, Arg2, Arg3)> function_;
Arg1 arg1_;
Arg2 arg2_;
Arg3 arg3_;
};
/*
Run functions
*/
namespace ConcurrentRun {
void Run(
template<typename ReturnType>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
std::tr1::function<void ()> function);
std::tr1::function<ReturnType ()> function) {
return (new ThreadFunctor0<ReturnType>(function))->Start(threadpool);
}
template<typename ReturnType, typename Arg>
QFuture<ReturnType> Run(
@ -150,6 +318,15 @@ namespace ConcurrentRun {
return (new ThreadFunctor2<ReturnType, Arg1, Arg2>(function, arg1, arg2))->Start(threadpool);
}
template<typename ReturnType, typename Arg1, typename Arg2, typename Arg3>
QFuture<ReturnType> Run(
QThreadPool* threadpool,
std::tr1::function<ReturnType (Arg1, Arg2, Arg3)> function,
const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) {
return (new ThreadFunctor3<ReturnType, Arg1, Arg2, Arg3>(function, arg1, arg2, arg3))->Start(threadpool);
}
}
#endif // CONCURRENTRUN_H

View File

@ -17,6 +17,7 @@
#include "config.h"
#include "songloader.h"
#include "core/concurrentrun.h"
#include "core/logging.h"
#include "core/song.h"
#include "core/signalchecker.h"
@ -38,7 +39,6 @@
#include <QFileInfo>
#include <QTimer>
#include <QUrl>
#include <QtConcurrentRun>
#include <QtDebug>
#include <boost/bind.hpp>
@ -229,7 +229,8 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
// inside right away.
if (QFileInfo(filename).isDir()) {
if (!block) {
QtConcurrent::run(this, &SongLoader::LoadLocalDirectoryAndEmit, filename);
ConcurrentRun::Run<void>(&thread_pool_,
boost::bind(&SongLoader::LoadLocalDirectoryAndEmit, this, filename));
return WillLoadAsync;
} else {
LoadLocalDirectory(filename);
@ -261,7 +262,8 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
// It's a playlist!
if (!block) {
QtConcurrent::run(this, &SongLoader::LoadPlaylistAndEmit, parser, filename);
ConcurrentRun::Run<void>(&thread_pool_,
boost::bind(&SongLoader::LoadPlaylistAndEmit, this, parser, filename));
return WillLoadAsync;
} else {
LoadPlaylist(parser, filename);

View File

@ -19,6 +19,7 @@
#define SONGLOADER_H
#include <QObject>
#include <QThreadPool>
#include <QUrl>
#include "song.h"
@ -129,6 +130,8 @@ private:
LibraryBackendInterface* library_;
boost::shared_ptr<GstElement> pipeline_;
QThreadPool thread_pool_;
};
#endif // SONGLOADER_H

View File

@ -187,7 +187,7 @@ DeviceManager::DeviceManager(Application* app, QObject *parent)
// This reads from the database and contends on the database mutex, which can
// be very slow on startup.
ConcurrentRun::Run(&thread_pool_, bind(&DeviceManager::LoadAllDevices, this));
ConcurrentRun::Run<void>(&thread_pool_, bind(&DeviceManager::LoadAllDevices, this));
// This proxy model only shows connected devices
connected_devices_model_ = new DeviceStateFilterModel(this);

View File

@ -33,8 +33,6 @@
# include "internet/spotifyservice.h"
#endif
#include <QtConcurrentRun>
const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000;
const int GstEnginePipeline::kFaderFudgeMsec = 2000;

View File

@ -4,14 +4,47 @@
#include <QFutureWatcher>
#include <QThreadPool>
#include <boost/bind.hpp>
#include "core/concurrentrun.h"
#include "test_utils.h"
int f() {
return 1337;
}
TEST(ConcurrentRunTest, ConcurrentRun0StartAndWait) {
QThreadPool threadpool;
QFuture<int> future = ConcurrentRun::Run<int>(&threadpool, &f);
QFutureWatcher<int> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(1337, watcher.result());
}
int g(int i) {
return ++i;
}
TEST(ConcurrentRunTest, ConcurrentRun1StartAndWait) {
QThreadPool threadpool;
int i = 1336;
QFuture<int> future = ConcurrentRun::Run<int, int>(&threadpool, &g, i);
QFutureWatcher<int> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(1337, watcher.result());
}
int max(int i, int j) {
return (i > j ? i : j);
}
TEST(ConcurrentRunTest, ConcurrentRunStartAndWait) {
TEST(ConcurrentRunTest, ConcurrentRun2StartAndWait) {
int i = 10;
int j = 42;
QThreadPool threadpool;
@ -24,6 +57,101 @@ TEST(ConcurrentRunTest, ConcurrentRunStartAndWait) {
EXPECT_EQ(42, watcher.result());
}
int sum(int a, int b, int c) {
return a + b + c;
}
TEST(ConcurrentRunTest, ConcurrentRun3StartAndWait) {
int i = 10;
int j = 42;
int k = 50;
QThreadPool threadpool;
QFuture<int> future = ConcurrentRun::Run<int, int, int, int>(&threadpool, &sum, i, j, k);
QFutureWatcher<int> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(102, watcher.result());
}
void aFunction(int* n) {
*n = 1337;
}
void bFunction(int* n, int *m) {
aFunction(n);
*m = 1338;
}
void cFunction(int* n, int *m, int *o) {
bFunction(n, m);
*o = 1339;
}
TEST(ConcurrentRunTest, ConcurrentRunVoidFunction1Start) {
QThreadPool threadpool;
int n = 10;
QFuture<void> future = ConcurrentRun::Run<void, int*>(&threadpool, &aFunction, &n);
QFutureWatcher<void> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(1337, n);
}
TEST(ConcurrentRunTest, ConcurrentRunVoidFunction2Start) {
QThreadPool threadpool;
int n = 10, m = 11;
QFuture<void> future = ConcurrentRun::Run<void, int*, int*>(&threadpool, &bFunction, &n, &m);
QFutureWatcher<void> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(1337, n);
EXPECT_EQ(1338, m);
}
TEST(ConcurrentRunTest, ConcurrentRunVoidFunction3Start) {
QThreadPool threadpool;
int n = 10, m = 11, o = 12;
QFuture<void> future = ConcurrentRun::Run<void, int*, int*, int*>(&threadpool, &cFunction, &n, &m, &o);
QFutureWatcher<void> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(1337, n);
EXPECT_EQ(1338, m);
EXPECT_EQ(1339, o);
}
class A {
public:
void f(int* i) {
*i = *i + 1;
}
};
TEST(ConcurrentRunTest, ConcurrentRunVoidBindFunctionStart) {
QThreadPool threadpool;
A a;
int nb = 10;
QFuture<void> future = ConcurrentRun::Run<void>(&threadpool, std::tr1::bind(&A::f, &a, &nb));
QFutureWatcher<void> watcher;
watcher.setFuture(future);
QEventLoop loop;
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
EXPECT_EQ(11, nb);
}
// TODO: add some more complex test cases? (e.g. with several CPU-consuming
// tasks launched in parallel, with/without threadpool's threads numbers
// decreased, etc.)