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();
}