Простое решение состоит в том, чтобы предотвратить проблему: если объект собирается стать безрезультатным, переместите его в родительский поток дескриптора потока, а затем, когда сам поток собирается разрушиться, восстановите таймеры объекта, чтобы предотвратить предупреждениеРеализация.
QObject
* moveToThread
состоит из двух частей:
QEvent::ThreadChange
доставляется объекту из moveToThread
.QObject::event
использует это событие для захвата и деактивации таймеров, активных на объекте.Эти таймеры упакованы в список и размещены во внутреннем методе _q_reactivateTimers
объекта.
Цикл событий в потоке назначения доставляет метаколл к объекту, _q_reregisterTimers
запускается в новом потоке, и таймеры повторно активируются в новом потоке.Обратите внимание, что если _q_reregisterTimers
не получит шанса на запуск, будет безвозвратно утечка списка таймеров .
Таким образом, нам нужноto:
Захватите момент, когда объект вот-вот станет безрезультатным, и переместите его в другой поток, чтобы значения QMetaCallEvent
до _q_reactivateTimers
не были потеряны.
Доставить событие в правильном потоке.
И так:
// https://github.com/KubaO/stackoverflown/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 предоставляет достаточно событий для такого отслеживания.