如何安全地销毁 QThread?
我想正确地销毁 Qt 5.3 中的 QThread
.
I want to properly destruct a QThread
in Qt 5.3.
到目前为止我有:
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:线程仍在运行时被销毁
QThread: Destroyed while thread is still running
推荐答案
安全线程
在 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 的成员.
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.
成员声明的顺序很重要,因为成员将按照声明的相反顺序被销毁.因此,MyClass
的析构函数将依次调用:
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:
m_workerThread.~Thread()
此时线程结束并消失,m_worker.thread() == 0
.
m_workerThread.~Thread()
At this point the thread is finished and gone, andm_worker.thread() == 0
.
m_worker.~Worker
由于对象是无线程的,所以在任何线程中销毁它都是安全的.
m_worker.~Worker
Since the object is threadless, it's safe to destroy it in any thread.
~QObject
因此,将工作线程及其线程作为 MyClass
的成员:
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();
}
};
而且,如果您不希望实现在接口中,那么使用 PIMPL 也是如此
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 成员出身
我们现在看到关于任何 QObject
成员的出身的硬性规则出现了.只有两种情况:
QObject Member Parentage
We now see a hard rule emerge as to the parentage of any QObject
members. There are only two cases:
如果
QObject
成员没有从类中移动到另一个线程,它必须是类的后代.
If a
QObject
member is not moved to another thread from within the class, it must be a descendant of the class.
否则,我们必须将 QObject
成员移动到另一个线程.成员声明的顺序必须使得线程在对象之前销毁.如果无效破坏驻留在另一个线程中的对象.
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.
只有在以下断言成立时才能安全地销毁 QObject
:
It is only safe to destruct a QObject
if the following assertion holds:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
线程被破坏的对象变成无线程的,并且!object->thread()
保持不变.
An object whose thread has been destructed becomes threadless, and !object->thread()
holds.
有人可能会争辩说我们不打算"将我们的班级转移到另一个线程.如果是这样,那么显然我们的对象不再是 QObject
,因为 QObject
具有 moveToThread
方法并且可以随时移动.如果一个类不遵守 Liskov 的替换原则到它的基类,这是一个错误从基类声明公共继承.因此,如果我们的类公开继承自QObject
,它必须允许自己随时移动到任何其他线程.
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.
QWidget
在这方面有点异常.至少,它应该使 moveToThread
成为受保护的方法.
The QWidget
is a bit of an outlier in this respect. At the very minimum, it should have made the moveToThread
a protected method.
例如:
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();
}
相关文章