Прежде всего, я прочитал QThread s и использую QEventL oop, но я не совсем уверен, что моя реализация верна.
TL; DR см. Описание проблемы ниже.
Наиболее полезными источниками информации являются Qt Wiki , KDAB Qthread презентация (полезно для w / o без события l oop), SO сообщения здесь и здесь , относящиеся к этому вопросу.
Мой сценарий:
У меня есть потенциально очень долго работающая функция с несколькими вызовами на дисках ввода / вывода. Таким образом, мне нужен поток, чтобы не блокировать пользовательский интерфейс. Для этого я создал собственную реализацию Thread.
TL; DR QThreads
Насколько я понимаю, QThread - это отдельное событие. l oop объект и требует либо пользовательской реализации run () , либо перемещения объекта во вновь созданный поток object , в котором перемещенные объекты живут (и работают) , Я описал это с реализацией события l oop.
Проблема
Возможно, мне что-то не хватает, так как моя реализация этого, что я описал выше, делает не работает правильно. Откуда мне это знать, хорошо, что в Qt Docs и SO выше упоминается, что QThread :: quit () или QThread :: exit () зависят от QEventL oop, и если QThread :: exe c () не был запущен (путем вызова QThread :: run () через QThread :: start () ), затем функции quit () или exit () никогда не запустятся, , что является моей проблемой .
Моя философия реализации похожа на синтаксис Thread & Lambda Java, например
new Thread(() -> { // some code to run in a different thread}).start();
Я использовал следующую реализацию
Контейнерный объект типа Thread, где можно использовать лямбды
QWorkerThread: public QObject
// This is the thread that runs object below
----QWaitThread : public QThread
// This is the object which lives inside the above thread
----ThreadWorker : public QObject, public QInterruptable
Простой пример использования будет (очистка потока и дочерних объектов выполняется внутри QWorkerThread
):
QWorkerThread *workerThread = new QWorkerThread;
workerThread->setRunnable([](){
// insert CPU intensive or long running task here
});
workerThread->start();
Описание проблемы / пример
// somewhere in main UI thread
workerThread->stop(); // or workerThread->kill()
, который вызывает QThread::quit()
или QThread::quit()
, затем QThread::terminate()
, за которым следует QThread::wait()
, не прервет поток , Длительный процесс, определенный в лямбде (внутри setRunnable()
), будет работать до тех пор, пока он не будет завершен.
Я знаю, что этот пост длиннее обычного, но я бы предпочел, чтобы все получили полное "изображение" о том, чего я пытаюсь достичь, так как я не уверен, в чем заключается моя проблема.
Буду признателен за любую помощь!
Реализация кода
Я буду публиковать весь код для полной идеи реализации, если я упущу что-то важное.
QWaitThread.h - реализация QThread
#ifndef QWAITTHREAD_H
#define QWAITTHREAD_H
#include <QObject>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
class QWaitThread : public QThread
{
Q_OBJECT
public:
explicit QWaitThread(QObject *parent = nullptr);
~QWaitThread();
virtual void pause();
virtual void resume();
signals:
void paused();
void resumed();
public slots:
void pausing();
void resuming();
private:
QWaitCondition *waitCondition;
QMutex mutex;
};
#endif // QWAITTHREAD_H
QWaitThread. cpp
#include "qwaitthread.h"
QWaitThread::QWaitThread(QObject *parent) : QThread(parent)
{
waitCondition = new QWaitCondition;
}
QWaitThread::~QWaitThread()
{
if(waitCondition != nullptr) {
delete waitCondition;
}
}
void QWaitThread::pause()
{
emit paused();
waitCondition->wait(&mutex);
}
void QWaitThread::resume()
{
waitCondition->wakeAll();
emit resumed();
}
void QWaitThread::pausing()
{
pause();
}
void QWaitThread::resuming()
{
resume();
}
Интерфейс QInterruptable.h определяет некоторые ожидаемые функции
#ifndef QINTERRUPTABLE_H
#define QINTERRUPTABLE_H
class QInterruptable {
public:
virtual void pause() = 0;
virtual void resume() = 0;
virtual void interrupt() = 0;
virtual ~QInterruptable() = default;
};
#endif // QINTERRUPTABLE_H
ThreadWorker.h - это объект, который живет (и работает) внутри QWaitThread
#ifndef THREADWORKER_H
#define THREADWORKER_H
#include <QObject>
#include <functional>
#include <QWaitCondition>
#include <QMutex>
#include "QInterruptable.h"
class ThreadWorker : public QObject, public QInterruptable
{
Q_OBJECT
private:
QMutex mutex;
QWaitCondition *waitCondition;
std::function<void ()> runnable;
bool shouldPause = false;
public:
explicit ThreadWorker(QObject *parent = nullptr);
ThreadWorker(std::function<void ()> func);
~ThreadWorker();
void setRunnable(const std::function<void ()> &value);
signals:
/**
* Emitted when the QWorkerThread object has started work
* @brief started
*/
void started();
/**
* @brief progress reports on progress in method, user defined.
* @param value reported using int
*/
void progress(int value);
/**
* Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
* @brief started
*/
void finished();
/**
* Emitted when the QWorkerThread has encountered an error, user defined.
* @brief started
*/
void error();
public slots:
virtual void run();
virtual void cleanup();
// QInterruptable interface
public:
void pause()
{
shouldPause = true;
}
void resume()
{
shouldPause = false;
}
QMutex& getMutex();
QWaitCondition *getWaitCondition() const;
void setWaitCondition(QWaitCondition *value);
bool getShouldPause() const;
// QInterruptable interface
public:
void interrupt()
{
}
};
#endif // THREADWORKER_H
ThreadWorker. cpp
#include "threadworker.h"
void ThreadWorker::setRunnable(const std::function<void ()> &value)
{
runnable = value;
}
QMutex& ThreadWorker::getMutex()
{
return mutex;
}
QWaitCondition *ThreadWorker::getWaitCondition() const
{
return waitCondition;
}
void ThreadWorker::setWaitCondition(QWaitCondition *value)
{
waitCondition = value;
}
bool ThreadWorker::getShouldPause() const
{
return shouldPause;
}
ThreadWorker::ThreadWorker(QObject *parent) : QObject(parent)
{
waitCondition = new QWaitCondition;
}
ThreadWorker::ThreadWorker(std::function<void ()> func): runnable(func) {
waitCondition = new QWaitCondition;
}
ThreadWorker::~ThreadWorker()
{
if(waitCondition != nullptr){
delete waitCondition;
}
}
void ThreadWorker::run()
{
emit started();
runnable();
emit finished();
}
void ThreadWorker::cleanup()
{
}
QWorkerThread.h основной класс интересов, где runnable lambda принято и где происходит обработка основного потока, перемещение в поток, запуск потока, обработка событий и т. д. c
#ifndef QWORKERTHREAD_H
#define QWORKERTHREAD_H
#include <QObject>
#include <functional>
#include <QThread>
#include <QEventLoop>
#include "qwaitthread.h"
#include "threadworker.h"
class QWorkerThread: public QObject
{
Q_OBJECT
public:
enum State {
Running,
Paused,
NotRunning,
Finished,
Waiting,
Exiting
};
QWorkerThread();
explicit QWorkerThread(std::function<void ()> func);
~QWorkerThread();
static QString parseState(QWorkerThread::State state);
virtual void setRunnable(std::function <void()> runnable);
virtual void start(QThread::Priority priority = QThread::Priority::InheritPriority);
virtual void stop();
virtual void wait(unsigned long time = ULONG_MAX);
virtual void kill();
virtual void setWorkerObject(ThreadWorker *value);
virtual void pause();
virtual void resume();
virtual QWaitThread *getWorkerThread() const;
State getState() const;
signals:
/**
* Emitted when the QWorkerThread object has started work
* @brief started
*/
void started();
/**
* @brief progress reports on progress in method, user defined.
* @param value reported using int
*/
void progress(int value);
/**
* Emitted when the QWorkerThread object has finished its work, same signal is used from &QThread::finished
* @brief started
*/
void finished();
/**
* Emitted when the QWorkerThread has encountered an error, user defined.
* @brief started
*/
void error();
private:
/**
* @brief workerObject - Contains the object and 'method' that will be moved to `workerThread`
*/
ThreadWorker *workerObject = nullptr;
/**
* @brief workerThread - Worker Thread is seperate thread that runs the method
*/
QWaitThread *workerThread = nullptr;
State state = State::NotRunning;
};
#endif // QWORKERTHREAD_H
QWorkerThread. cpp реализация
#include "qworkerthread.h"
QWorkerThread::QWorkerThread()
{
state = State::NotRunning;
workerThread = new QWaitThread;
workerObject = new ThreadWorker;
workerThread->setObjectName("WorkerThread");
}
QWorkerThread::QWorkerThread(std::function<void ()> func)
{
state = State::NotRunning;
workerThread = new QWaitThread;
workerObject = new ThreadWorker(func);
workerThread->setObjectName("WorkerThread");
}
QWorkerThread::~QWorkerThread()
{
// Check if worker thread is running
if(workerThread->isRunning()) {
// Exit thread with -1
workerThread->exit(-1);
}
if(!workerThread->isFinished()) {
workerThread->wait(500);
if(workerThread->isRunning()) {
workerThread->terminate();
}
}
// cleanup
delete workerObject;
delete workerThread;
}
void QWorkerThread::setRunnable(std::function<void ()> runnable)
{
workerObject->setRunnable(runnable);
}
void QWorkerThread::start(QThread::Priority priority)
{
state = State::Running;
// Connect workerThread start signal to ThreadWorker object's run slot
connect(workerThread, &QThread::started, workerObject, &ThreadWorker::started);
connect(workerThread, &QThread::started, workerObject, &ThreadWorker::run);
// Connect threadWorker progress report to this progress report
connect(workerObject, &ThreadWorker::progress, this, &QWorkerThread::progress);
// Cleanup
connect(workerObject, &ThreadWorker::finished, this, [this](){
state = State::Finished;
emit finished();
});
connect(workerThread, &QWaitThread::finished, this, [this] {
workerObject->deleteLater();
});
// move workerObject to thread
workerObject->moveToThread(workerThread);
// emit signal that we are starting
emit started();
// Start WorkerThread which invokes object to start process method
workerThread->start(priority);
}
void QWorkerThread::stop()
{
state = State::Exiting;
// Exit thread safely with success
workerThread->quit();
emit finished();
}
void QWorkerThread::wait(unsigned long time)
{
state = State::Waiting;
workerThread->wait(time);
}
void QWorkerThread::kill()
{
// try stopping
stop();
// check if still running
if(workerThread->isRunning()){
// forcefully kill
workerThread->terminate();
workerThread->wait();
}
emit finished();
}
void QWorkerThread::setWorkerObject(ThreadWorker *value)
{
workerObject = value;
}
QWaitThread *QWorkerThread::getWorkerThread() const
{
return workerThread;
}
QWorkerThread::State QWorkerThread::getState() const
{
return state;
}
QString QWorkerThread::parseState(QWorkerThread::State state) {
switch (state) {
case Running:
return "Running";
case Paused:
return "Paused";
case NotRunning:
return "NotRunning";
case Finished:
return "Finished";
case Waiting:
return "Waiting";
case Exiting:
return "Exiting";
}
return QString("Unknown State [%1]").arg(QString::number(state)) ;
}
void QWorkerThread::pause()
{
workerObject->pause();
state = State::Paused;
}
void QWorkerThread::resume()
{
workerObject->resume();
state = State::Running;
}
Обновление с некоторой дополнительной информацией
Что касается ~QWorkerThread()
, я заметил, что при вызове delete QThread
или QThread::deleteLater()
, QWaitThread()
(или QThread
) выдаст фатальную ошибку : поток уничтожен, пока он еще работает. Это после того, как quit()
/ terminate()
был вызван.
Следующая строка из QThread. cpp
if (d->running && !d->finished && !d->data->isAdopted)
qFatal("QThread: Destroyed while thread is still running");
где
d->running == true
d->finished == false
d->data->isAdopted ?