First off, I have read up on QThread s 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 eg
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 ?
I've tested your code and here is what I realized.
As you mentioned, terminate()
doesn't completely stop a thread. Qt doc says:
Terminates the execution of the thread. The thread may or may not be terminated immediately, depending on the operating system's scheduling policies. Use
QThread::wait()
afterterminate()
, to be sure.
Unfortunately, the wait()
freezes even after terminate()
. It might be a problem with your code, but I created a maximally simplified example to verify this, which still has the same problems.
Firstly, here is the part of your code what I would suggest to change:
QWorkerThread::~QWorkerThread()
{
...
// cleanup
delete workerObject; // Unsafe, but the only way to call the destructor, if necessary
delete workerThread; // qFatal
}
Here is what Qt doc says about the destructor unsafety:
Calling delete on a QObject
from a thread other than the one that owns the object (or accessing the object in other ways) is unsafe , unless you guarantee that the object isn't processing events at that moment. Use QObject::deleteLater()
instead, and a DeferredDelete
event will be posted, which the event loop of the object's thread will eventually pick up. By default, the thread that owns a QObject
is the thread that creates the QObject
, but not after QObject::moveToThread()
has been called.
Note. Changing delete workerThread
to workerThread->deleteLater()
works for me without qFatal
.
QThread
subclass cannot be called directly after terminate()
due to qFatal
wait()
freezes and cannot be used after terminate()
despite to docs(seems the problem is actual only when an infinite operation is moved into event loop)
Worker.h
#pragma once
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
~Worker();
public slots:
void process();
};
Worker.cpp
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>
Worker::~Worker()
{
qDebug() << "~Worker()";
}
void Worker::process()
{
qDebug("Hello World!");
while(true)
{
qDebug() << QDateTime::currentDateTime();
QThread::msleep(100);
}
}
MainWin.h
#pragma once
#include <QtWidgets/QMainWindow>
class QThread;
class Worker;
class MainWin : public QMainWindow
{
Q_OBJECT
public:
MainWin(QWidget *parent = nullptr);
~MainWin();
private:
QThread* thread = nullptr;
Worker* worker = nullptr;
};
MainWin.cpp
#include "MainWin.h"
#include "Worker.h"
#include <QThread>
#include <QDebug>
#include <QDateTime>
MainWin::MainWin(QWidget *parent)
: QMainWindow(parent)
{
thread = new QThread;
worker = new Worker;
worker->moveToThread(thread);
// Start only one infinite operation
connect(thread, &QThread::started, worker, &Worker::process);
thread->start();
}
MainWin::~MainWin()
{
if (thread->isRunning())
{
thread->exit(-1);
thread->wait(500);
}
if (thread->isRunning())
{
thread->terminate();
}
//cleanup
delete worker;
delete thread; // qFatal("QThread: Destroyed while thread is still running")
}
MainWin::~MainWin()
{
...
//cleanup
delete worker; // Worker destructor will be called, but be note this is unsafe
thread->deleteLater(); // Allows to avoid qFatal but make thread terminated
}
Everything I can offer besides to avoid terminate()
at all, is to use terminate()
without wait()
and then workerThread->deleteLater()
.
If time-consuming operation which you're trying to terminate, is your own code, consider embedding some terminate flag into the code.
It would be better to avoid raw pointers and replace they by smart pointers, where possible.
Simplified example how can one use lamdas, signals-slots, threads, started-finished signals, QtConcurrent::run()
and QFuture<>
. This way you can achieve both running code in one persistent additional thread and inside an automatic thread pool as well. But termination is not supported.
LambdaThread.h
#pragma once
#include <QObject>
#include <functional>
#include <QFuture>
class QThreadPool;
class LambdaThread : public QObject
{
Q_OBJECT
public:
// maxThreadCount = -1 to use idealThreadCount by default
LambdaThread(QObject *parent, int maxThreadCount = -1);
signals:
void started();
void finished();
public slots:
// Invoke this directly or by a signal
QFuture<void> setRunnable(std::function<void()> func);
private:
/*
For the case you need persistent thread sometimes.
In the case you never need persistent thread,
just remove m_threadPool from this class at all
*/
QThreadPool* m_threadPool = nullptr;
};
LambdaThread.cpp
#include "LambdaThread.h"
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>
LambdaThread::LambdaThread(QObject *parent, int maxThreadCount /*= -1*/)
: QObject(parent)
{
m_threadPool = new QThreadPool(this);
if(maxThreadCount > 0)
{
m_threadPool->setMaxThreadCount(maxThreadCount);
if (maxThreadCount == 1)
{
// Avoid thread affinity changing
m_threadPool->setExpiryTimeout(-1);
}
}
}
QFuture<void> LambdaThread::setRunnable(std::function<void()> func)
{
return QtConcurrent::run(m_threadPool,
[this, func]()
{
// Be note that you actually need event loop in a receiver thread only
emit started();
func();
emit finished();
});
}
Just GUI class example where you can start your runnables and receive signals.
MainWin.h
#pragma once
#include <QtWidgets/QMainWindow>
#include <functional>
class LambdaThread;
class MainWin : public QMainWindow
{
Q_OBJECT
public:
MainWin(QWidget *parent = nullptr);
signals:
// For the case you want to use signals
void newRunnable(std::function<void()> func);
private:
LambdaThread* m_lambdaThread = nullptr;
};
MainWin.cpp
#include "MainWin.h"
#include "LambdaThread.h"
#include <QFuture>
#include <QDebug>
MainWin::MainWin(QWidget *parent)
: QMainWindow(parent)
{
m_lambdaThread = new LambdaThread(this);
connect(this, &MainWin::newRunnable,
m_lambdaThread, &LambdaThread::setRunnable);
/*
Do not forget the third (`this`) context variable
while using modern signal-slot connection syntax with lambdas
*/
connect(m_lambdaThread, &LambdaThread::started,
this, []()
{
qDebug() << "Runnable stated";
});
connect(m_lambdaThread, &LambdaThread::finished,
this, []()
{
qDebug() << "Runnable finished";
});
// Set your lambda directly
QFuture<void> future = m_lambdaThread->setRunnable([]()
{
qDebug() << "hello from threaded runnable";
});
// You can also use future (not necessary of course)
//future.waitForFinished();
// Or you can emit your lambda via the signal:
emit newRunnable([]()
{
qDebug() << "hello from threaded runnable which comes from signal";
});
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.