如何安全地破坏QThread?

Nik*_*las 5 c++ qt qthread

我想QThread在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();
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,在一天结束时,我仍然得到:

QThread:线程仍在运行时被销毁

Rei*_*ica 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();
}
Run Code Online (Sandbox Code Playgroud)

它会表现得恰到好处.

QObject成员不需要堆

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

成员声明的顺序很重要,因为成员将按照与声明相反的顺序进行破坏.因此,MyClasswill 的析构函数按顺序调用:

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

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

  3. ~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();
  }
};
Run Code Online (Sandbox Code Playgroud)

并且,如果您不希望实现在接口中,则使用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()
{}
Run Code Online (Sandbox Code Playgroud)

QObject成员父母

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

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

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

QObject如果以下断言成立,则破坏a是安全的:

Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
Run Code Online (Sandbox Code Playgroud)

线程被破坏的对象变为无线,并!object->thread()保持.

有人可能会争辩说,我们并没有"打算"将我们的类转移到另一个线程.如果是这样,那么显然我们的对象QObject不再是,因为a QObjectmoveToThread方法并且可以随时移动.如果一个类不服从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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Niklas有三种方法可以破坏"拥有"的"QObject".你错过了#2和#3的可能性.1.在堆上构造并使其成为子项 - 如果要在不移动其父项的情况下将对象单独移动到另一个线程,则不起作用.2.在堆上构造,*没有父*,并使用智能指针(`QScopedPointer`或`std :: unique_ptr`).3.使其成为直接(非指针)成员,没有父级.要使#2和#3工作,必须在*生成在该线程上的对象的实例之前破坏线程*. (2认同)