問(wèn)題描述
我想正確地銷(xiāo)毀 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();
}
我的問(wèn)題是在一天結(jié)束時(shí)我仍然得到:
My problem is that at the end of the day I still get:
QThread:線程仍在運(yùn)行時(shí)被銷(xiāo)毀
QThread: Destroyed while thread is still running
推薦答案
安全線程
在 C++ 中,類(lèi)的正確設(shè)計(jì)是可以隨時(shí)安全地銷(xiāo)毀實(shí)例.幾乎所有 Qt 類(lèi)都以這種方式運(yùn)行,但 QThread
不會(huì).
這是您應(yīng)該使用的類(lèi):
// 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();
}
它會(huì)表現(xiàn)得很好.
另一個(gè)問(wèn)題是Worker
對(duì)象會(huì)被泄露.不要將所有這些對(duì)象放在堆上,只需讓它們成為 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.
成員聲明的順序很重要,因?yàn)槌蓡T將按照聲明的相反順序被銷(xiāo)毀.因此,MyClass
的析構(gòu)函數(shù)將依次調(diào)用:
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()
此時(shí)線程結(jié)束并消失,m_worker.thread() == 0
.
m_workerThread.~Thread()
At this point the thread is finished and gone, andm_worker.thread() == 0
.
m_worker.~Worker
由于對(duì)象是無(wú)線程的,所以在任何線程中銷(xiāo)毀它都是安全的.
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();
}
};
而且,如果您不希望實(shí)現(xiàn)在接口中,那么使用 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 成員出身
我們現(xiàn)在看到關(guān)于任何 QObject
成員的出身的硬性規(guī)則出現(xiàn)了.只有兩種情況:
QObject Member Parentage
We now see a hard rule emerge as to the parentage of any QObject
members. There are only two cases:
如果
QObject
成員沒(méi)有從類(lèi)中移動(dòng)到另一個(gè)線程,它必須是類(lèi)的后代.
If a
QObject
member is not moved to another thread from within the class, it must be a descendant of the class.
否則,我們必須將 QObject
成員移動(dòng)到另一個(gè)線程.成員聲明的順序必須使得線程在對(duì)象之前銷(xiāo)毀.如果無(wú)效破壞駐留在另一個(gè)線程中的對(duì)象.
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.
只有在以下斷言成立時(shí)才能安全地銷(xiāo)毀 QObject
:
It is only safe to destruct a QObject
if the following assertion holds:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
線程被破壞的對(duì)象變成無(wú)線程的,并且!object->thread()
保持不變.
An object whose thread has been destructed becomes threadless, and !object->thread()
holds.
有人可能會(huì)爭(zhēng)辯說(shuō)我們不打算"將我們的班級(jí)轉(zhuǎn)移到另一個(gè)線程.如果是這樣,那么顯然我們的對(duì)象不再是 QObject
,因?yàn)?QObject
具有 moveToThread
方法并且可以隨時(shí)移動(dòng).如果一個(gè)類(lèi)不遵守 Liskov 的替換原則到它的基類(lèi),這是一個(gè)錯(cuò)誤從基類(lèi)聲明公共繼承.因此,如果我們的類(lèi)公開(kāi)繼承自QObject
,它必須允許自己隨時(shí)移動(dòng)到任何其他線程.
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
在這方面有點(diǎn)異常.至少,它應(yīng)該使 moveToThread
成為受保護(hù)的方法.
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();
}
這篇關(guān)于如何安全地銷(xiāo)毀 QThread?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!