First off, I have read up on QThreads and using the QEventLoop but I am not entirely sure my implementation is correct.
TL;DR see Problem Details below.
Most useful sources of information is Qt Wiki, KDAB Qthread presentation (useful for w/ & w/o event loop), SO posts here and here relating to this question.
My scenario is:
I have a potentially very long running function with multiple I/O disk calls. Thus I need a thread to not block the UI. For this, I made my own implementation of a Thread.
TL;DR QThreads
My understanding is a QThread is a separate event-loop object, and requires either a custom run() implementation or have an object moved to the newly created thread object wherein the moved object(s) lives (and runs). What I described is w/ the event loop implementation.
Problem
I maybe be missing something as my implementation of this, what I described above, does not function correctly. How do I know this, well Qt Docs and SO posts above mention that QThread::quit() or QThread::exit() are dependent on the QEventLoop, and if the QThread::exec() was not run (by calling the QThread::run() via QThread::start()), then the quit() or exit() functions will never run, which is my problem.
My implementation philisophy is somethings similar to Java's Thread & Lambda syntax e.g.
new Thread(() -> { // some code to run in a different thread}).start();
I used the following implementation
Thread Object container of sorts where lambdas can be used
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
Simple example usage would be (thread and child object cleanup done inside QWorkerThread
):
QWorkerThread *workerThread = new QWorkerThread;
workerThread->setRunnable([](){
// insert CPU intensive or long running task here
});
workerThread->start();
Problem Detail/Example
// somewhere in main UI thread
workerThread->stop(); // or workerThread->kill()
which calls QThread::quit()
or QThread::quit()
, then QThread::terminate()
followed by QThread::wait()
will not terminate the thread. The long running process defined in the lambda (inside setRunnable()
) will run until it is complete.
I know this post is longer that what is conventional, but I would prefer everyone to get the full 'picture' of what I am trying to achieve, as I am unsure of where my problem actually lies.
Any help would be gratefully appreciated!
Code Implementation
I will be posting all code for a full idea of implementation, incase I miss something important.
QWaitThread.h is the implementation of a 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 interface defines some expected functionality
#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 is the Object that lives (and runs) inside 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 the main class of interest, where the runnable lambda is accepted and where the main 'thread' processing happens, moving to thread, starting thread, handling events, etc
#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 implementation
#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;
}
Update with Some extra info
Regarding the ~QWorkerThread()
, I noticed that when calling delete QThread
or QThread::deleteLater()
, the QWaitThread()
(or QThread
) will throw a Fatal error: Thread destroyed while it is still running. This is after quit()
/terminate()
was called.
The following line from QThread.cpp
if (d->running && !d->finished && !d->data->isAdopted)
qFatal("QThread: Destroyed while thread is still running");
where
d->running == true
d->finished == false
d->data->isAdopted ?