Рабочий класс для многопоточности в Qt

Я думал о рабочем классе, который можно использовать для параллельных вычислений.

Я хотел избежать ситуации, когда я вынужден явно перемещать работника в отдельный поток после создания объекта, т.е.

Worker worker;
QThread thread;
worker.moveToThread(&thread);
thread.start();
worker.start();

Это решение, которое я придумал:

Заголовок:

#include <QObject>
#include <QThread>

class ThreadedWorker : public QObject
{
Q_OBJECT
public:
explicit ThreadedWorker(QObject *parent = 0);
signals:
void finished(ThreadedWorker* worker);
public slots:
void start();
protected:
virtual void run();
QThread workerThread_;
bool isRunning_;
};

Источник:

#include "threadedworker.h"
ThreadedWorker::ThreadedWorker(QObject *parent) : QObject(parent)
{
this->moveToThread(&this->workerThread_);
this->workerThread_.start();
this->isRunning_ = false;
}

void ThreadedWorker::start()
{
if(!this->isRunning_)
{
this->isRunning_ = true;
this->run();
}
}

void ThreadedWorker::run()
{
// HERE GOES THE ACTUAL WORK FOR THE WORKER
//
//
emit this->finished(this); // added to inform a controller object that the worker has finished
}

ОБНОВЛЕНО после комментария Zailborg:

Так что теперь я просто создаю:

ThreadedWorker worker1;
ThreadedWorker worker2;

вызвать их start() слоты по некоторому внешнему сигналу, и они работают параллельно.

Однако моя главная проблема заключается в том, не является ли это плохой практикой QThread workerThread_ как член ThreadedWorker класс и перемещение объекта в этот поток в конструкторе.

1

Решение

не является ли плохой практикой помещать QThread workerThread_ в качестве члена класса ThreadedWorker

Когда объект перемещается в его поток, объект и его дочерние элементы перемещаются. Дочерние объекты — это объекты, связанные в дочерней иерархии Qt parent, когда вы передаете родительский элемент в конструктор производного класса QObject или вызываете setParent.

В случае указателя члена, такого как QThread указатель, это не «ребенок» класса. Поэтому, когда объект ThreadedWorker перемещается в новый поток, это будет работать. Однако могут возникнуть проблемы из-за путаницы схожести потоков. Основной объект перемещается в новый поток, но содержит указатель на элемент, объект которого связан с другим потоком; объект, на который указывает QThread *.

Код, представленный в вопросе, ссылается не на указатель QThread, а на экземпляр QThread. Имея это в виду, рассмотрим документацию для QObject :: moveToThread когда говорится:

Предупреждение: Эта функция не является поточно-ориентированной; текущий поток должен совпадать с текущим сродством потока. Другими словами, эта функция может только «вытолкнуть» объект из текущего потока в другой поток, она не может «вытянуть» объект из любого произвольного потока в текущий поток.

Итак, я подозреваю, что дизайн QThread и QObject :: moveToThread ожидает, что сходство QThread стабильно и не будет изменено. Будучи членом перемещаемого объекта, это не так.

По этой причине я бы сказал, что не очень хорошая идея иметь экземпляр QThread в качестве члена QObject и перемещать объект в этот поток.

Если вы собираетесь использовать QThread, тогда я предлагаю прочитать и следовать методу, описанному в Как на самом деле, действительно использовать QThread.

Кроме того, часто упускается из виду, что функциональность moveToThread допускает отношение QThread от 1 ко многим к QObject, поэтому вполне приемлемо создать объект QThread и переместить несколько экземпляров QObject в новый поток. Кроме того, обычно мало пользы в создании большего количества потоков, чем в доступных процессорных ядрах.

2

Другие решения

посмотри на: Как правильно реализовать QThread … (пример, пожалуйста …)

и в дополнение к моему комментарию, я не думаю, что это сохранение, чтобы выделить объект в стеке и переместить его в другой поток.

0

Для всех, кто интересуется, небольшой вспомогательный класс, позволяющий создавать рабочих с автоматическим вычитанием аргументов с переменным числом аргументов. Обратите внимание, что требуется C ++ 17 (то есть, при котором указание аргументов шаблона не требуется). Полный исходный код см .: https://github.com/Broekman/Qt5_template

worker.hpp

#ifndef QT5_UI_WORKER_HPP
#define QT5_UI_WORKER_HPP

#include <QObject>
#include <QString>
#include <functional>
#include <tuple>

namespace ui
{
class worker_object :
public QObject
{
Q_OBJECT

public:
inline worker_object() = default;
inline ~worker_object() = default;

public slots:
inline virtual void run() { /*...*/ };

signals:
void finished();
void error(const QString& err_msg);
};

namespace helper
{
template <int... Is>
struct index {};

template <int N, int... Is>
struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

template <int... Is>
struct gen_seq<0, Is...> : index<Is...> {};
}

template<typename... Ts>
class worker :
public worker_object
{
public: /* Functions */
template<typename Func, typename... Args>
inline worker(Func&& fn, Args&& ... args) :
fn_(std::forward<Func>(fn)),
args_(std::forward<Args>(args)...)
{ /*...*/ }

inline ~worker() = default;

inline void run() override
{
func(args_);
emit finished();
}

private: /* Functions */
template<typename... Args, int... Is>
inline void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
fn_(std::get<Is>(tup)...);
}

template<typename... Args>
inline void func(std::tuple<Args...>& tup)
{
func(tup, helper::gen_seq<static_cast<int>(sizeof...(Args))>{});
}

private: /* Class members */
std::function<void(Ts...)> fn_;
std::tuple<Ts...> args_;
};

/**
* @brief Helper function to create a worker by which specification of the template arguments aren't necessary.
*/
template<typename Func, typename... Args>
worker<Args...> make_worker(Func&& fn, Args&& ... args)
{
return worker<Args...>(std::forward<Func>(fn), std::forward<Args>(args)...);
}
}

#endif

main_window.cpp

    void main_window::start_worker(worker_object *thread_worker, const worker_callback& on_finish,
const worker_error_callback& on_error)
{
auto *worker_thread = new QThread;
thread_worker->moveToThread(worker_thread);
connect(thread_worker, &worker_object::error, this, on_error);
connect(worker_thread, &QThread::started, thread_worker, &worker_object::run);
connect(thread_worker, &worker_object::finished, worker_thread, &QThread::quit);
connect(thread_worker, &worker_object::finished, this, on_finish);
connect(thread_worker, &worker_object::finished, thread_worker, &worker_object::deleteLater);
connect(worker_thread, &QThread::finished, worker_thread, &QThread::deleteLater);
worker_thread->start();
}

main_window.cpp пример 1: без аргументов

void main_window::service_example()
{
//STEP 1: specify a (synchronous) task for the worker.
auto *work = new worker(make_worker([this]()
{
auto task_len_ms = 2500; //ms
logger_->info("Doing some concurrent work for " + std::to_string(task_len_ms) + " milliseconds...");
QThread::msleep((unsigned)task_len_ms);
}));

//STEP 2: specify the completion handler. Called upon a worker_object::finished signal.
auto on_finish_callback = [this]()
{
logger_->info("Concurrent work finished!");
};

//STEP 3: specify an error handler. Called upon a worker_object::error(const QString&) signal.
auto on_error_callback = [this](const QString& err_msg)
{
logger_->error(err_msg.toStdString());
};

//STEP 4: start the worker.
start_worker(work, on_finish_callback, on_error_callback);
}

main_window.cpp пример 2: некоторые аргументы

//STEP 1: specify a (synchronous) task for the worker.
auto *work = new worker(make_worker([this](const std::string& personal_msg, unsigned long wait_time)
{
logger_->info(personal_msg);
QThread::msleep((unsigned)wait_time);
}, "Hello, world?", 2500));

//STEP 2: specify the completion handler. Called upon a worker_object::finished signal.
//STEP 3: specify an error handler. Called upon a worker_object::error(const QString&) signal.
//STEP 4: start the worker.
0
По вопросам рекламы [email protected]