当对象变为无线程时,如何防止 QBasicTimer::stop: Failed 警告? [英] How to prevent the QBasicTimer::stop: Failed warning when objects become threadless?

查看:76
本文介绍了当对象变为无线程时,如何防止 QBasicTimer::stop: Failed 警告?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

QObjects 很容易成为无线程的,当它们的工作线程在它们之前完成时.发生这种情况时,Qt 不会释放它们的计时器 ID,即使计时器不再处于活动状态.因此,一个 QBasicTimer::stop: Failed.可能试图从不同的线程停止 警告出现.它主要是外观上的后果,但确实表明计时器 id 泄漏,因此有一个解决方法会很好.下面的例子触发了这个问题:

QObjects can easily become threadless, when their work thread finishes ahead of them. When this happens, Qt doesn't release their timer ids, even though the timers are not active anymore. Thus, a QBasicTimer::stop: Failed. Possibly trying to stop from a different thread warning appears. It has mostly cosmetic consequences, but does indicate a timer id leak, and thus a workaround would be nice to have. The following example triggers the problem:

#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 已经做的之外,不需要额外的定时器跟踪.

It'd be nice if the workaround didn't have to make any modifications to how the timers are allocated, i.e. that there would be no extra tracking of timers needed beyond what Qt already does.

推荐答案

一个简单的解决方案是防止这个问题:如果对象即将成为无线程的,将它移动到线程句柄的父线程,然后当线程本身即将被破坏,重新建立对象的计时器以防止警告.

A simple solution is to prevent the problem: if the object is about to become threadless, move it to the thread handle's parent thread, and then when the thread itself is about to be destructed, reestablish the object's timers to prevent the warning.

QObjectmoveToThread 实现有两部分:

QObject's moveToThread implementation has two parts:

  1. QEvent::ThreadChangemoveToThread 传递给对象.QObject::event 使用此事件来捕获和停用对象上活动的计时器.这些计时器打包在一个列表中,并发布到对象的内部 _q_reactivateTimers 方法.

  1. The QEvent::ThreadChange is delivered to the object from moveToThread. QObject::event uses this event to capture and deactivate the timers active on the object. Those timers are packaged in a list and posted to the object's internal _q_reactivateTimers method.

目标线程中的事件循环将元调用传递给对象,_q_reregisterTimers 在新线程中运行,计时器在新线程中重新激活.请注意,如果 _q_reregisterTimers 没有机会运行,它将不可撤销地泄漏计时器列表.

The event loop in the destination thread delivers the metacall to the object, the _q_reregisterTimers runs in the new thread and the timers get reactivated in the new thread. Note that if _q_reregisterTimers doesn't get a chance to run, it will irrevocably leak the timer list.

因此我们需要:

  1. 捕捉对象即将成为无线程的时刻,并将其移动到不同的线程,这样 QMetaCallEvent_q_reactivateTimers 不会丢失.

在正确的线程中传递事件.

Deliver the event in the correct thread.

所以:

// 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 提供了足够的事件来进行此类跟踪.

This approach can be easily extended to dynamically track all children of obj as well: Qt provides sufficient events to do such tracking.

这篇关于当对象变为无线程时,如何防止 QBasicTimer::stop: Failed 警告?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆