如何安全地销毁QThread
How to safely destruct a QThread?
我想在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:在线程仍在运行时销毁
安全线程
在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();
}
它将表现得适当。
Q 成员不需要在堆上
另一个问题是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()
{}
Q会员亲子关系
我们现在看到,关于任何QObject
成员的父母身份,出现了一条硬性规定。只有两种情况:
如果
QObject
成员未从类中移动到另一个线程,则该成员必须是该类的后代。否则,我们必须将
QObject
成员移动到另一个线程。成员声明的顺序必须使线程在对象之前被销毁。如果无效,则析构驻留在另一个线程中的对象。
只有当以下断言成立时,销毁QObject
才是安全的:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
线程已被破坏的对象变为无线程,并且!object->thread()
保持。
有人可能会争辩说,我们并不"打算"将我们的类移动到另一个线程。如果是这样,那么显然我们的对象不再是QObject
,因为QObject
具有moveToThread
方法并且可以随时移动。如果一个类不遵守其基类的 Liskov 替换原则,那么从基类声明公共继承是错误的。因此,如果我们的类公开继承自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();
}
mThread->requestInterruption(( 不会立即停止线程,它只是向正在运行的代码发出干净结束的信号的一种方式(您必须检查 isInterruptionRequest(( 并自己停止计算(。
来自Qt文档:
请求中断线程。该请求是建议性的,由线程上运行的代码决定是否以及如何对此类请求进行操作。此函数不会停止线程上运行的任何事件循环,也不会以任何方式终止它。 另请参阅 isInterruptionRequest((。
- 从不同线程使用int64的不同字节安全吗
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 虚拟决赛作为安全
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 通过网络、跨平台传递std::变体是否安全
- 如何监控QThread
- 在std::thread中,joinable()然后join()线程安全吗
- 使用std::istream::peek()总是安全的吗
- 从值小于256的uint16到uint8的Endian安全转换
- 在c++队列中使用pop和visit实现线程安全
- 在类型和包装器之间reinterpret_cast是否安全<Type>?
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 安全到标准:移动会员?
- AcquireCredentialsHandleA() 返回 PFX 文件的0x8009030e(安全包中没有可用的凭据
- 共享队列的线程安全
- QThread 通信线程安全
- QThread:从不同线程修改变量的安全方法