Как предотвратить предупреждение QBasicTimer::stop: Failed, когда объекты становятся поточными?
QObject
Они могут легко стать безрезультатными, когда их рабочий поток заканчивается. Когда это происходит, Qt не выпускает свои идентификаторы таймера, даже если таймеры больше не активны. Таким образом, QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
появляется предупреждение. Это имеет в основном косметические последствия, но указывает на утечку идентификатора таймера, и, таким образом, было бы неплохо найти обходной путь. Следующий пример вызывает проблему:
#include <QtCore>
int main(int argc, char *argv[]) {
static_assert(QT_VERSION < QT_VERSION_CHECK(5,11,0), "");
QCoreApplication app(argc, argv);
QObject object;
object.startTimer(1000);
QThread workThread;
workThread.start();
object.moveToThread(&workThread);
QTimer::singleShot(500, &QCoreApplication::quit);
app.exec();
workThread.quit();
workThread.wait();
}
Было бы неплохо, если бы для обходного пути не пришлось вносить какие-либо изменения в то, как распределяются таймеры, то есть не было бы необходимости в дополнительном отслеживании таймеров, помимо того, что уже делает Qt.
3 ответа
Простое решение состоит в том, чтобы предотвратить проблему: если объект собирается стать безрезультатным, переместите его в родительский поток дескриптора потока, а затем, когда сам поток собирается разрушиться, восстановите таймеры объекта, чтобы предотвратить предупреждение.
QObject
"s moveToThread
Реализация состоит из двух частей:
QEvent::ThreadChange
доставлен на объект изmoveToThread
,QObject::event
использует это событие для захвата и деактивации таймеров, активных на объекте. Эти таймеры упакованы в список и размещены во внутреннем объекте_q_reactivateTimers
метод.Цикл событий в целевом потоке доставляет метаколл к объекту,
_q_reregisterTimers
работает в новом потоке, и таймеры повторно активируются в новом потоке. Обратите внимание, что если_q_reregisterTimers
не получится запустить, он безвозвратно утечет список таймеров.
Таким образом, нам необходимо:
Захватите момент, когда объект собирается стать безрезьбовым, и переместите его в другой поток, чтобы
QMetaCallEvent
в_q_reactivateTimers
не будет потеряноДоставить событие в правильном потоке.
Так что:
// https://github.com/KubaO/stackrun/tree/master/questions/qbasictimer-stop-fix-50636079
#include <QtCore>
class Thread final : public QThread {
Q_OBJECT
void run() override {
connect(QAbstractEventDispatcher::instance(this),
&QAbstractEventDispatcher::aboutToBlock,
this, &Thread::aboutToBlock);
QThread::run();
}
QAtomicInt inDestructor;
public:
using QThread::QThread;
/// Take an object and prevent timer resource leaks when the object is about
/// to become threadless.
void takeObject(QObject *obj) {
// Work around to prevent
// QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
static constexpr char kRegistered[] = "__ThreadRegistered";
static constexpr char kMoved[] = "__Moved";
if (!obj->property(kRegistered).isValid()) {
QObject::connect(this, &Thread::finished, obj, [this, obj]{
if (!inDestructor.load() || obj->thread() != this)
return;
// The object is about to become threadless
Q_ASSERT(obj->thread() == QThread::currentThread());
obj->setProperty(kMoved, true);
obj->moveToThread(this->thread());
}, Qt::DirectConnection);
QObject::connect(this, &QObject::destroyed, obj, [obj]{
if (!obj->thread()) {
obj->moveToThread(QThread::currentThread());
obj->setProperty(kRegistered, {});
}
else if (obj->thread() == QThread::currentThread() && obj->property(kMoved).isValid()) {
obj->setProperty(kMoved, {});
QCoreApplication::sendPostedEvents(obj, QEvent::MetaCall);
}
else if (obj->thread()->eventDispatcher())
QTimer::singleShot(0, obj, [obj]{ obj->setProperty(kRegistered, {}); });
}, Qt::DirectConnection);
obj->setProperty(kRegistered, true);
}
obj->moveToThread(this);
}
~Thread() override {
inDestructor.store(1);
requestInterruption();
quit();
wait();
}
Q_SIGNAL void aboutToBlock();
};
int main(int argc, char *argv[]) {
static_assert(QT_VERSION < QT_VERSION_CHECK(5,11,0), "");
QCoreApplication app(argc, argv);
QObject object1, object2;
object1.startTimer(10);
object2.startTimer(200);
Thread workThread1, workThread2;
QTimer::singleShot(500, &QCoreApplication::quit);
workThread1.start();
workThread2.start();
workThread1.takeObject(&object1);
workThread2.takeObject(&object2);
app.exec();
}
#include "main.moc"
Этот подход может быть легко расширен для динамического отслеживания всех детей obj
также: Qt предоставляет достаточно событий для такого отслеживания.
Держите идентификатор таймера, который будет уничтожен из потока object
:
int id = object.startTimer(1000);
QThread workThread;
workThread.start();
object.moveToThread(&workThread);
QTimer::singleShot(500, &QCoreApplication::quit);
QObject::connect(&workThread, &QThread::finished, [&](){object.killTimer(id);});
...
Как насчет перемещения объекта обратно в основной поток...
class Object : public QObject
{
public:
using QObject::QObject;
virtual ~Object() {
qDebug()<<"Object"<<QThread::currentThread()<<this->thread();
if(thread() == Q_NULLPTR)
moveToThread(QThread::currentThread());
}
};
#include <QtCore>
int main(int argc, char *argv[]) {
static_assert(QT_VERSION < QT_VERSION_CHECK(5,11,0), "");
QCoreApplication app(argc, argv);
Object object;
object.startTimer(1000);
QThread workThread;
workThread.start();
object.moveToThread(&workThread);
QTimer::singleShot(500, &QCoreApplication::quit);
qDebug()<<"main"<<QThread::currentThread()<<object.thread();
app.exec();
workThread.quit();
workThread.wait();
}