Как безопасно уничтожить 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();
}
Моя проблема в том, что в конце дня я все еще получаю:
QThread: уничтожено во время работы потока
2 ответа
Безопасная нить
В 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
будет вызывать, по порядку:
m_workerThread.~Thread()
На этом этапе поток закончен и ушел, иm_worker.thread() == 0
,m_worker.~Worker
Поскольку объект является поточным, его можно уничтожить в любом потоке.~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();
}
};
И, если вы не хотите, чтобы реализация находилась в интерфейсе, то же самое с использованием 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
элемент не перемещен в другой поток из класса, он должен быть потомком класса.В противном случае мы должны переместить
QObject
участник в другой теме. Порядок объявлений членов должен быть таким, что поток должен быть уничтожен перед объектом. Если недопустимо уничтожить объект, который находится в другом потоке.
Это безопасно только уничтожить QObject
если верно следующее утверждение:
Q_ASSERT(!object->thread() || object->thread() == QThread::currentThread())
Объект, поток которого был разрушен, становится безрезультатным, и !object->thread()
держит.
Можно утверждать, что мы не "намерены" переместить наш класс в другой поток. Если так, то, очевидно, наш объект не является QObject
больше, так как QObject
имеет moveToThread
метод и может быть перемещен в любое время. Если класс не подчиняется принципу подстановки Лискова своему базовому классу, было бы ошибкой требовать открытого наследования от базового класса. Таким образом, если наш класс публично наследует от 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();
}
mThread->requestInterruption() не останавливает поток мгновенно, это всего лишь один из способов дать понять, что ваш работающий код окончательно завершен (вы должны проверить isInterruptionRequested() и остановить вычисления самостоятельно).
Из Qt документов:
Запросить прерывание потока. Этот запрос носит рекомендательный характер, и от кода, работающего в потоке, зависит, будет ли и как он должен реагировать на такой запрос. Эта функция не останавливает цикл обработки событий в потоке и не прерывает его каким-либо образом. Смотрите также isInterruptionRequested().