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

查看:236
本文介绍了如何安全地销毁QThread?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在Qt 5.3中正确销毁 QThread



到目前为止, p>

  MyClass :: MyClass(QObject * parent):QObject(parent){
mThread = new QThread(this);
QObject :: connect(mThread,SIGNAL(finished()),mThread,SLOT(deleteLater()));
mWorker = new Worker(); //继承自QObject
mWorker-> moveToThread(mThread);
mThread-> start();
}

MyClass ::〜MyClass(){
mThread-> requestInterruption();
}

我的问题是,在一天结束时我还是得到: p>


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



解决方案

安全线程



在C ++中,类的正确设计使得实例可以随时安全地销毁。几乎所有的Qt类都这样做,但 QThread 没有。



这里是你应该使用的类:

  // Thread.hpp 
#include< QThread>
public Thread:class QThread {
Q_OBJECT
使用QThread :: run; //这是一个最终类
public:
Thread(QObject * parent = 0);
〜Thread();
}

// Thread.cpp
#includeThread.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 ol>

    因此,工作线程作为 MyClass 的成员:

      class MyClass:public QObject {
    Q_OBJECT
    Worker m_worker; // ** NOT **指向Worker的指针
    线程m_workerThread; // ** NOT **指向Thread的指针!
    public:
    MyClass(QObject * parent = 0):QObject(parent),
    // m_worker **不能有父类,因为我们将它移动到另一个线程。
    // m_workerThread **必须**有父级。 MyClass可以随时移动到另一个
    //线程。
    m_workerThread(this)
    {
    m_worker.moveToThread(& m_workerThread);
    m_workerThread.start();
    }
    };

    如果你不想在接口中使用实现, p>

      // MyClass.hpp 
    #include< QObject>
    class MyClassPrivate;
    class MyClass:public QObject {
    Q_OBJECT
    Q_DECLARE_PRIVATE(MyClass)
    QScopedPointer public:
    MyClass(QObject * parent = 0);
    〜MyClass(); // required!
    }

    // MyClass.cpp
    #includeMyClass.h
    #includeThread.h

    class MyClassPrivate {
    public:
    工人; // ** NOT **指向Worker的指针
    Thread workerThread; // ** NOT **指向Thread的指针!
    MyClassPrivate(QObject * parent);
    };

    MyClassPrivate(QObject * parent):
    // worker **不能有一个parent,因为我们把它移动到另一个线程。
    // workerThread **必须**有父级。 MyClass可以随时移动到另一个
    //线程。
    workerThread(parent)
    {}

    MyClass :: MyClass(QObject * parent):QObject(parent),
    d_ptr(new MyClassPrivate b $ b {
    Q_D(MyClass);
    d-> worker.moveToThread(& d-> workerThread);
    d-> workerThread.start();
    }

    MyClass ::〜MyClass()
    {}



    QObject Member Parentage



    我们现在看到一个关于任何 QObject 成员。只有两种情况:


    1. 如果 QObject


    2. 否则,我们必须在类中移动到另一个线程,必须是类的后代 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)//定时器是我们的孩子
    //如果m_timer不是我们的child,`worker.moveToThread`构造之后
    //会导致定时器失败。
    {}

    bool Worker :: processFile(const QString& fn){
    QScopedPointer< QFile> file(new QFile(fn,this));
    //如果文件不是我们的孩子,`processFile`之后的`moveToThread`将
    //导致文件失败。
    if(!file-> open(QIODevice :: ReadOnly))return false;
    m_files<< file.take();
    }


    I want to properly destruct a QThread in Qt 5.3.

    So far I have got:

    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: Destroyed while thread is still running

    解决方案

    The Safe Thread

    In C++, the proper design of a class is such that the instance can be safely destroyed at any time. Almost all Qt classes act that way, but QThread doesn't.

    Here's the class you should be using instead:

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

    It will behave appropriately.

    QObject Members Don't Need to Be on the Heap

    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.

    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() At this point the thread is finished and gone, and m_worker.thread() == 0.

    2. m_worker.~Worker Since the object is threadless, it's safe to destroy it in any thread.

    3. ~QObject

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

    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 Member Parentage

    We now see a hard rule emerge as to the parentage of any QObject members. There are only two cases:

    1. If a QObject member is not moved to another thread from within the class, it must be a descendant of the class.

    2. 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.

    It is only safe to destruct a QObject if the following assertion holds:

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

    An object whose thread has been destructed becomes threadless, and !object->thread() holds.

    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.

    The QWidget is a bit of an outlier in this respect. At the very minimum, it should have made the moveToThread a protected method.

    For example:

    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天全站免登陆