c++ qt qthread

c++ - ¿Cómo destruir de forma segura un QThread?



(2)

Quiero destruir correctamente un QThread en Qt 5.3.

Hasta ahora tengo:

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

Mi problema es que al final del día aún recibo:

QThread: Destruido mientras el hilo todavía se está ejecutando


El hilo seguro

En C ++, el diseño adecuado de una clase es tal que la instancia se puede destruir de forma segura en cualquier momento. Casi todas las clases de Qt actúan de esa manera, pero QThread no lo hace.

Aquí está la clase que deberías usar en su lugar:

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

Se comportará apropiadamente.

Los miembros de QObject no necesitan estar en el montón

Otro problema es que el objeto Worker se filtrará. En lugar de poner todos esos objetos en el montón, simplemente conviértelos en miembros de MyClass o su PIMPL.

El orden de las declaraciones de los miembros es importante , ya que los miembros serán destruidos en el orden inverso de la declaración . Por lo tanto, el destructor de MyClass invocará en orden:

  1. m_workerThread.~Thread() En este punto, el hilo ha terminado y se ha acabado, y m_worker.thread() == 0 .

  2. m_worker.~Worker Dado que el objeto no tiene hilos, es seguro destruirlo en cualquier hilo.

  3. ~QObject

Por lo tanto, con el trabajador y su hilo como miembros de 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(); } };

Y, si no quiere que la implementación esté en la interfaz, lo mismo con 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

Ahora vemos surgir una regla dura en cuanto a la filiación de cualquier miembro de QObject . Solo hay dos casos:

  1. Si un miembro de QObject no se mueve a otro hilo desde dentro de la clase, debe ser un descendiente de la clase.

  2. De lo contrario, debemos mover el miembro de QObject a otro hilo. El orden de las declaraciones de los miembros debe ser tal que el hilo se destruya antes que el objeto. Si no es válido para destruir un objeto que reside en otro hilo.

Solo es seguro destruir un QObject si se cumple la siguiente afirmación:

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

Un objeto cuyo hilo ha sido destruido se convierte en threadless, y !object->thread() mantiene.

Uno podría argumentar que no "intentamos" que nuestra clase se mueva a otro hilo. Si es así, entonces obviamente nuestro objeto ya no es un objeto QObject , ya que un QObject tiene el método moveToThread y se puede mover en cualquier momento. Si una clase no obedece el principio de sustitución de Liskov a su clase base, es un error reclamar la herencia pública de la clase base. Por lo tanto, si nuestra clase hereda públicamente de QObject , debe permitir que se mueva a cualquier otro tema en cualquier momento.

El QWidget es un poco atípico a este respecto. Como mínimo, debería haber hecho que moveToThread un método protegido.

Por ejemplo:

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 () no detiene el hilo instantáneamente, es solo una forma de indicarle a su código de ejecución que finalice limpiamente (debe verificar isInterruptionRequested () y detener el cálculo usted mismo).

De Qt docs:

Solicite la interrupción del hilo. Esa solicitud es consultiva y depende del código que se ejecuta en el hilo para decidir si debe actuar al recibir dicha solicitud y cómo debe hacerlo. Esta función no detiene ningún bucle de evento que se ejecute en el hilo y no lo termina de ninguna manera. Consulte también isInterruptionRequested ().