安全线索
在 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();
}
它会表现得适当。
QObject 成员不需要位于堆上
另一个问题是Worker
对象将会被泄露。不要将所有这些对象放在堆上,只需将它们设为成员即可MyClass
或其 PIMPL。
成员声明的顺序是重要的,因为成员将在声明顺序相反。因此,析构函数MyClass
将按顺序调用:
m_workerThread.~Thread()
至此,线程已完成并消失,并且m_worker.thread() == 0
.
m_worker.~Worker
由于该对象是无线程的,因此在任何线程中销毁它都是安全的。
~QObject
因此,工作人员及其线程作为成员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
成员。只有两种情况:
If a QObject
成员不会从类内移动到另一个线程,它必须是后裔班级的。
否则,我们must移动QObject
另一个线程的成员。成员声明的顺序必须使得线程能够之前被摧毁物体。如果是invalid销毁驻留在另一个线程中的对象。
只有摧毁一个才是安全的QObject
如果以下断言成立:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
线程被破坏的对象将变成无线程的,并且!object->thread()
holds.
有人可能会说我们不“打算”将我们的类移动到另一个线程。如果是这样,那么显然我们的对象不是QObject
不再,自从QObject
有moveToThread
方法,并且可以随时移动。如果一个类不遵守里氏替换原则 http://en.wikipedia.org/wiki/Liskov_substitution_principle对于其基类,从基类声明公共继承是错误的。那么如果我们班publicly继承自QObject
, it must允许自己随时移动到任何其他线程。
The 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();
}