Как предотвратить предупреждение QBasicTimer :: stop: Failed, когда объекты становятся поточными? - PullRequest
0 голосов
/ 01 июня 2018

QObject s могут легко стать безрезьбовыми, когда их рабочий поток заканчивается.Когда это происходит, 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 ]

0 голосов
/ 01 июня 2018

Простое решение состоит в том, чтобы предотвратить проблему: если объект собирается стать безрезультатным, переместите его в родительский поток дескриптора потока, а затем, когда сам поток собирается разрушиться, восстановите таймеры объекта, чтобы предотвратить предупреждениеРеализация.

QObject * moveToThread состоит из двух частей:

  1. QEvent::ThreadChange доставляется объекту из moveToThread.QObject::event использует это событие для захвата и деактивации таймеров, активных на объекте.Эти таймеры упакованы в список и размещены во внутреннем методе _q_reactivateTimers объекта.

  2. Цикл событий в потоке назначения доставляет метаколл к объекту, _q_reregisterTimers запускается в новом потоке, и таймеры повторно активируются в новом потоке.Обратите внимание, что если _q_reregisterTimers не получит шанса на запуск, будет безвозвратно утечка списка таймеров .

Таким образом, нам нужноto:

  1. Захватите момент, когда объект вот-вот станет безрезультатным, и переместите его в другой поток, чтобы значения QMetaCallEvent до _q_reactivateTimers не были потеряны.

  2. Доставить событие в правильном потоке.

И так:

// 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 предоставляет достаточно событий для такого отслеживания.

0 голосов
/ 01 июня 2018

Как насчет перемещения объекта обратно в основной поток ...

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();
}
0 голосов
/ 01 июня 2018

Удержание идентификатора таймера, который будет уничтожен из потока - на 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);});

...
...