我想在Qt 5.3中正确销毁QThread
。
到目前为止,我有:
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();
}
我的问题是,在一天结束时我仍然得到:
QThread:在线程仍在运行时被销毁
答案 0 :(得分:8)
在C ++中,类的正确设计使得实例可以随时被安全地销毁。几乎所有Qt类都采用这种方式,但QThread
没有。
以下是您应该使用的课程:
// 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();
}
它会表现得恰到好处。
另一个问题是Worker
对象将被泄露。而不是将所有这些对象放在堆上,只需将它们设为MyClass
或其PIMPL的成员。
成员声明的顺序是重要,因为成员将以声明的相反顺序被破坏。因此,MyClass
的析构函数将按顺序调用:
m_workerThread.~Thread()
此时线程已完成并消失,m_worker.thread() == 0
。
m_worker.~Worker
由于该对象是无线程的,因此可以在任何线程中将其销毁。
~QObject
因此,将worker及其主题作为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();
}
};
并且,如果您不希望实现在接口中,则使用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
成员的父母身份的硬规则。只有两种情况:
如果QObject
成员未被移动到班级内的另一个帖子,则必须是班级的后代。
否则,我们必须将QObject
成员移动到另一个线程。成员声明的顺序必须使得线程在对象之前被销毁。如果无效则破坏驻留在另一个线程中的对象。
如果以下断言成立,则只能安全地销毁QObject
:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
线程被破坏的对象变为无线,!object->thread()
成立。
有人可能会争辩说我们并没有“打算”将我们的课程转移到另一个主题。如果是这样,那么显然我们的对象不再是QObject
,因为QObject
具有moveToThread
方法,并且可以随时移动。如果类不遵守其基类的Liskov's substitution principle,则从基类声明公共继承是错误的。因此,如果我们的班级公开继承自QObject
,那么必须允许自己随时移动到任何其他线程。
QWidget
在这方面有点异常。它至少应该使moveToThread
成为受保护的方法。
例如:
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();
}
答案 1 :(得分:2)
mThread-&gt; requestInterruption()不会立即停止线程,它只是一种方式来表示正在运行的代码干净地结束(你必须检查isInterruptionRequested()并自己停止计算)。
来自Qt docs:
请求中断线程。该请求是建议性的,由线程上运行的代码来决定它是否以及如何根据此类请求执行操作。此函数不会停止在线程上运行的任何事件循环,也不会以任何方式终止它。 另请参见isInterruptionRequested()。