如何安全地销毁 QThread? [英] How to safely destruct a QThread?

查看:58
本文介绍了如何安全地销毁 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:

  1. m_workerThread.~Thread() 此时线程结束并消失,m_worker.thread() == 0.

  1. m_workerThread.~Thread() At this point the thread is finished and gone, and m_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:

  1. 如果 QObject 成员没有从类中移动到另一个线程,它必须是类的后代.

  1. 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆