問題描述
我想正確地銷毀 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();
}
這篇關于如何安全地銷毀 QThread?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!