如何安全地破坏QThread?

时间:2014-08-10 01:02:38

标签: c++ qt 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:在线程仍在运行时被销毁

2 个答案:

答案 0 :(得分:8)

安全线程

在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();
}

它会表现得恰到好处。

QObject成员不需要在堆

另一个问题是Worker对象将被泄露。而不是将所有这些对象放在堆上,只需将它们设为MyClass或其PIMPL的成员。

成员声明的顺序是重要,因为成员将以声明的相反顺序被破坏。因此,MyClass的析构函数将按顺序调用:

  1. m_workerThread.~Thread()此时线程已完成并消失,m_worker.thread() == 0

  2. m_worker.~Worker由于该对象是无线程的,因此可以在任何线程中将其销毁。

  3. ~QObject

  4. 因此,将worker及其主题作为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()
    {}
    

    QObject成员父母

    我们现在看到一个关于任何QObject成员的父母身份的硬规则。只有两种情况:

    1. 如果QObject成员未被移动到班级内的另一个帖子,则必须是班级的后代

    2. 否则,我们必须QObject成员移动到另一个线程。成员声明的顺序必须使得线程在对象之前被销毁。如果无效则破坏驻留在另一个线程中的对象。

    3. 如果以下断言成立,则只能安全地销毁QObject

      Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
      

      线程被破坏的对象变为无线,!object->thread()成立。

      有人可能会争辩说我们并没有“打算”将我们的课程转移到另一个主题。如果是这样,那么显然我们的对象不再是QObject,因为QObject具有moveToThread方法,并且可以随时移动。如果类不遵守其基类的Liskov's substitution principle,则从基类声明公共继承是错误的。因此,如果我们的班级公开继承自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();
      }
      

答案 1 :(得分:2)

mThread-&gt; requestInterruption()不会立即停止线程,它只是一种方式来表示正在运行的代码干净地结束(你必须检查isInterruptionRequested()并自己停止计算)。

来自Qt docs:

  

请求中断线程。该请求是建议性的,由线程上运行的代码来决定它是否以及如何根据此类请求执行操作。此函数不会停止在线程上运行的任何事件循环,也不会以任何方式终止它。   另请参见isInterruptionRequested()。