Threads no Clementine
Codigos Usando QThreadPool
No fragmento de codigo abaixo, é criada uma nova QThreadPool, e usa a função setMaxThreadCount()
para setar a quantidade de Threads que serão utilizadas.
Utiliza também a função activeThreadCount()
, que verifica o número de encadeamentos ativos, e a usa para determinar a condição de parada do loop while, verificando se
o mesmo é menor que número máximo de encadeamentos usados pelo conjunto de encadeamentos maxThreadCount()
.
Conta ainda com a função start()
, que executa a função runnable
, não setando prioridade para a mesma.
Arquivo: Clementine/src/covers/albumcoverexporter.cpp
#include "albumcoverexporter.h"
#include <QFile>
#include <QThreadPool>
#include "coverexportrunnable.h"
#include "core/song.h"
const int AlbumCoverExporter::kMaxConcurrentRequests = 3;
AlbumCoverExporter::AlbumCoverExporter(QObject* parent)
: QObject(parent),
thread_pool_(new QThreadPool(this)),
exported_(0),
skipped_(0),
all_(0){
thread_pool_->setMaxThreadCount(kMaxConcurrentRequests);
}
.
.
.
void AlbumCoverExporter::AddJobsToPool(){
while(!requests_.isEmpty() &&
thread_pool_-> activeThreadCount() < thread_pool_->maxThreadCount()){
CoverExportRunnable* runnable = requests_.dequeue();
connect(runnable, SIGNAL(CoverExported()), SLOT(CoverExported()));
connect(runnable, SIGNAL(CoverSkipped()), SLOT(CoverSkipped()));
thread_pool_->start(runnable);
}
}
No fragmento de código abaixo, é criado um conjunto de encadeamentos, usando a função Thread(QObject* parent = nullptr)
.
A função run()
inicia o segmento. Depois de chamar start(), o thread recém-criado chama essa função. A implementação padrão simplesmente chama exec().
Arquivo: Clementine/src/core/thread.h
#ifndef CORE_THREAD_H_
#define CORE_THREAD_H_
#include <QThread>
#include "core/utilities.h"
class Thread:public QThread{
public:
Thread(QObject* parent = nullptr)
: QThread(parent), io_priority_(Utilities::IOPRIO_CLASS_NONE){}
void SetIoPriority(Utilities::IoPriority priority){
io_priority_ = priority;
}
virtual void run() override;
private:
Utilities::IoPriority io_priority_;
};
#endif
Codigos Usando QThread
No fragmento de código abaixo é criada uma nova Thread.
A função moveToThread()
altera a afinidade de encadeamento para este objeto e seus filhos, mas não pode ser movido se tiver um pai.
A função start()
começa a execução do encadeamento chamando run().O sistema
operacional agendará o encadeamento de acordo com o parâmetro de prioridade. Se o encadeamento já estiver em execução, essa função não fará nada.
A função quit()
diz ao loop de eventos do encadeamento para sair com o código de
retorno 0 (sucesso). Equivalente a chamar exit(0)
.Essa função não faz nada se o segmento não tiver um loop de eventos.
#include "deletefiles.h"
#include <QStringList>
#include <QTimer>
#include <QThread>
#include <QUrl>
#include "musicstorage.h"
#include "taskmanager.h"
const int DeleteFiles::kBatchSize = 50;
DeleteFiles::DeleteFiles(TaskManager* task_manager,std::shared_ptr<MusicStorage> storage)
: thread_(nullptr),
task_manager_(task_manager),
storage_(storage),
started_(false),
task_id_(0),
progress_(0) {
original_thread_ = thread();
}
DeleteFiles::~DeleteFiles() {}
void DeleteFiles::Start(const SongList& songs) {
if (thread_) return;
songs_ = songs;
task_id_ = task_manager_->StartTask(tr("Deleting files"));
task_manager_->SetTaskBlocksLibraryScans(true);
thread_ = new QThread;
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
moveToThread(thread_);
thread_->start();
}
void DeleteFiles::ProcessSomeFiles() {
if (!started_) {
storage_->StartDelete();
started_ = true;
}
if (progress_ >= songs_.count()) {
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
storage_->FinishCopy(songs_with_errors_.isEmpty());
task_manager_->SetTaskFinished(task_id_);
emit Finished(songs_with_errors_);
moveToThread(original_thread_);
deleteLater();
thread_->quit();
return;
}
.
.
.
}
No fragmento de código abaixo, é executada a função isRunning()
, que verifica se um encadeamento está em execução, retorna true se o encadeamento estiver em execução, caso contrário, retorna false.
A função wait()
bloqueia o encadeamento associado a este objeto QThread terminou a execução ou retorna true se o encadeamento ainda não foi iniciado.
A função currentThread()
retorna um ponteiro para um QThread que gerencia o thread atualmente em execução.
A função idealThreadCount()
retorna o número ideal de encadeamentos que podem ser executados no sistema. Isso é feito consultando o número de núcleos de processador, reais e lógicos, no sistema. Esta função retorna 1 se o número de núcleos do processador não puder ser detectado.
A função finished()
emite um sinal antes do o encadeamento associado terminar a execução.
A função started()
emite um sinal quando um encadeamento associado inicia a execução, antes que a função run()
seja chamada.
Arquivo: Clementine/src/moodbar/moodbarloader.cpp
#include "moodbarloader.h"
#include <memory>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QNetworkDiskCache>
#include <QTimer>
#include <QThread>
#include <QUrl>
#include "moodbarpipeline.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/qhash_qurl.h"
#include "core/utilities.h"
#ifdef Q_OS_WIN32
#include <windows.h>
#endif
MoodbarLoader::MoodbarLoader(Application* app, QObject* parent)
: QObject(parent),
cache_(new QNetworkDiskCache(this)),
thread_(new QThread(this)),
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
save_alongside_originals_(false),
disable_moodbar_calculation_(false) {
cache_->setCacheDirectory(
Utilities::GetConfigPath(Utilities::Path_MoodbarCache));
cache_->setMaximumCacheSize(60 * 1024 *1024);
connect(app, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
ReloadSettings();
}
MoodbarLoader::~MoodbarLoader() {
thread_->quit();
thread_->wait(1000);
}
MoodbarLoader::Result MoodbarLoader::Load(const QUrl& url, QByteArray* data,MoodbarPipeline** async_pipeline){
.
.
.
if(!thread_->isRunning()) thread_->start(QThread::IdlePriority);
MoodbarPipeline* pipeline = new MoodbarPipeline(url);
pipeline->moveToThread(thread_);
NewClosure(pipeline, SIGNAL(Finished(bool)), this,
SLOT(RequestFinished(MoodbarPipeline*, QUrl)), pipeline, url);
requests_[url] = pipeline;
queued_requests_ << url;
MaybeTakeNextRequest();
*async_pipeline = pipeline;
return WillLoadAsync;
}
void MoodbarLoader::MaybeTakeNextRequest(){
Q_ASSERT(QThread::currentThread() == qApp->thread());
if(active_requests_.count() >= kMaxActiveRequests ||
queued_requests_.isEmpty() || disable_moodbar_calculation_) {
return;
}
const QUrl url = queued_requests_.takeFirst();
active_requests_ << url;
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
QMetaObject::invokeMethod(requests_[url], "Start", Qt::QueuedConnection);
}
No fragmento de código abaixo, a função exit()
diz ao loop de eventos do encadeamento para sair com um código de retorno. Depois de chamar essa função, o segmento deixa o loop de eventos e retorna da chamada para exec()
. Por convenção, um return de 0 significa sucesso, qualquer valor diferente de zero indica um erro.
Arquivo: Clementine/src/devices/filesystemdevice.cpp
FilesystemDevice::~FilesystemDevice() {
watcher_->deleteLater();
watcher_thread_->exit();
watcher_thread_->wait();
}