6

I want to properly destruct a QThread in Qt 5.3.

So far I have got:

MyClass::MyClass(QObject *parent) : QObject(parent) {
    mThread = new QThread(this);
    QObject::connect(mThread, SIGNAL(finished()), mThread, SLOT(deleteLater()));
    mWorker = new Worker(); // inherits from QObject
    mWorker->moveToThread(mThread);
    mThread->start();
}

MyClass::~MyClass() {
    mThread->requestInterruption();
}

My problem is that at the end of the day I still get:

QThread: Destroyed while thread is still running

sandwood
  • 2,038
  • 20
  • 38
Niklas
  • 23,674
  • 33
  • 131
  • 170
  • Thanks for asking this question. It needs good, canonical answers, and others need to be referenced here. It seem some variant of this issue pops up at least once a week just in the [qt] tag. – Kuba hasn't forgotten Monica Aug 10 '14 at 17:16
  • Thanks for your post. It really did help me to understand whats going on. – Niklas Aug 10 '14 at 18:32
  • almost duplicate of https://stackoverflow.com/questions/28660852/qt-qthread-destroyed-while-thread-is-still-running-during-closing – sandwood Nov 09 '17 at 18:05

2 Answers2

10

The Safe Thread

In C++, the proper design of a class is such that the instance can be safely destroyed at any time. Almost all Qt classes act that way, but QThread doesn't.

Here's the class you should be using instead:

// Thread.hpp
#include <QThread>
public Thread : class QThread {
  Q_OBJECT
  using QThread::run; // This is a final class
public:
  Thread(QObject * parent = 0);
  ~Thread();
}

// Thread.cpp
#include "Thread.h"
Thread::Thread(QObject * parent): QThread(parent)
{}

Thread::~Thread() {
  quit();
  #if QT_VERSION >= QT_VERSION_CHECK(5,2,0)
  requestInterruption();
  #endif
  wait();
}

It will behave appropriately.

QObject Members Don't Need to Be on the Heap

Another problem is that the Worker object will be leaked. Instead of putting all of those objects on the heap, simply make them members of MyClass or its PIMPL.

The order of member declarations is important, since the members will be destructed in the reverse order of declaration. Thus, the destructor of MyClass will, invoke, in order:

  1. m_workerThread.~Thread() At this point the thread is finished and gone, and m_worker.thread() == 0.

  2. m_worker.~Worker Since the object is threadless, it's safe to destroy it in any thread.

  3. ~QObject

Thus, with the worker and its thread as members of MyClass:

class MyClass : public QObject {
  Q_OBJECT
  Worker m_worker;          // **NOT** a pointer to Worker!
  Thread m_workerThread;    // **NOT** a pointer to Thread!
public:
  MyClass(QObject *parent = 0) : QObject(parent),
  // The m_worker **can't** have a parent since we move it to another thread.
  // The m_workerThread **must** have a parent. MyClass can be moved to another
  // thread at any time.
    m_workerThread(this)
  {
    m_worker.moveToThread(&m_workerThread);
    m_workerThread.start();
  }
};

And, if you don't want the implementation being in the interface, the same using PIMPL

// MyClass.hpp
#include <QObject>
class MyClassPrivate;
class MyClass : public QObject {
  Q_OBJECT
  Q_DECLARE_PRIVATE(MyClass)
  QScopedPointer<MyClass> const d_ptr;
public:
  MyClass(QObject * parent = 0);
  ~MyClass(); // required!
}

// MyClass.cpp
#include "MyClass.h"
#include "Thread.h"

class MyClassPrivate {
public:
  Worker worker;          // **NOT** a pointer to Worker!
  Thread workerThread;    // **NOT** a pointer to Thread!
  MyClassPrivate(QObject * parent);
};

MyClassPrivate(QObject * parent) :
  // The worker **can't** have a parent since we move it to another thread.
  // The workerThread **must** have a parent. MyClass can be moved to another
  // thread at any time.
    workerThread(parent)
{}

MyClass::MyClass(QObject * parent) : QObject(parent),
  d_ptr(new MyClassPrivate(this))
{
  Q_D(MyClass);
  d->worker.moveToThread(&d->workerThread);
  d->workerThread.start();
}

MyClass::~MyClass()
{}

QObject Member Parentage

We now see a hard rule emerge as to the parentage of any QObject members. There are only two cases:

  1. If a QObject member is not moved to another thread from within the class, it must be a descendant of the class.

  2. Otherwise, we must move the QObject member to another thread. The order of member declarations must be such that the thread is to be destroyed before the object. If is invalid to destruct an object that resides in another thread.

It is only safe to destruct a QObject if the following assertion holds:

Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())

An object whose thread has been destructed becomes threadless, and !object->thread() holds.

One might argue that we don't "intend" our class to be moved to another thread. If so, then obviously our object is not a QObject anymore, since a QObject has the moveToThread method and can be moved at any time. If a class doesn't obey the Liskov's substitution principle to its base class, it is an error to claim public inheritance from the base class. Thus, if our class publicly inherits from QObject, it must allow itself to be moved to any other thread at any time.

The QWidget is a bit of an outlier in this respect. At the very minimum, it should have made the moveToThread a protected method.

For example:

class Worker : public QObject {
  Q_OBJECT
  QTimer m_timer;
  QList<QFile*> m_files;
  ...
public:
  Worker(QObject * parent = 0);
  Q_SLOT bool processFile(const QString &);
};

Worker::Worker(QObject * parent) : QObject(parent),
  m_timer(this)  // the timer is our child
  // If m_timer wasn't our child, `worker.moveToThread` after construction
  // would cause the timer to fail.
{}

bool Worker::processFile(const QString & fn) {
  QScopedPointer<QFile> file(new QFile(fn, this));
  // If the file wasn't our child, `moveToThread` after `processFile` would
  // cause the file to "fail".
  if (! file->open(QIODevice::ReadOnly)) return false;      
  m_files << file.take();
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • `Worker` object gets leaked, because I did not create it with a parent object right? But then I will encounter: Objects with a parent can not be moved to a QThread. – Niklas Aug 10 '14 at 18:26
  • I just implemented it and it does work. My understanding is that if I have my Worker object and do something like `mWorker = new Worker(this)` it gets deleted by its parent (this) automatically and I don't have to invoke `delete mWorker` – Niklas Aug 10 '14 at 18:35
  • 1
    @Niklas Sure. But workers that are moved to other threads **can't** have a parent. That's why you don't even want them on the heap. Just use them as regular, non-pointer members. The owning class's destructor will take care of them. A lot of Qt code out there creates things explicitly on the heap for no reason at all. If an object's lifetime matches that of the owning class, then simply make it a regular, non-pointer member. The pointer fetish has many downsides. The first downside is that you're adding an extra heap allocation. That's premature pessimization, entirely due to sloppiness. – Kuba hasn't forgotten Monica Aug 10 '14 at 18:42
  • That's what I have said in my first comment. `But then I will encounter: Objects with a parent can not be moved to a QThread`Think we missunderstood each other. Anyway thank you very much, I really do appreciate your help. – Niklas Aug 10 '14 at 18:44
  • 2
    @Niklas There are three ways of destructing an "owned" `QObject`. You've missed the possibility #2 and #3. 1. Construct on the heap and make it a child - doesn't work if the object is to be separately moved to another thread without moving its parent, too. 2. Construct on the heap, *without a parent*, and use a smart pointer (`QScopedPointer` or `std::unique_ptr`). 3. Make it a direct (non-pointer) member, without a parent. For #2 and #3 to work, the thread must be destructed *before* the instance of the object that lived on that thread. – Kuba hasn't forgotten Monica Aug 10 '14 at 19:01
2

mThread->requestInterruption() does not stops the thread instantly, it is just one way to signal your running code to end cleanly (you must check isInterruptionRequested() and stop the computation yourself).

From Qt docs:

Request the interruption of the thread. That request is advisory and it is up to code running on the thread to decide if and how it should act upon such request. This function does not stop any event loop running on the thread and does not terminate it in any way. See also isInterruptionRequested().

Kknd
  • 3,033
  • 26
  • 29
  • Yeah I know that, normally I would check for `isInterruptionRequested` in the `run` method, but since I don't have one, I don't know how to handle that. – Niklas Aug 10 '14 at 10:22
  • @Niklas, you must create a subclass of `QThread` and reimplement `run()`. – NG_ Aug 10 '14 at 10:37