首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何防止对象无线程时出现QBasicTimer::stop: Failed警告?

如何防止对象无线程时出现QBasicTimer::stop: Failed警告?
EN

Stack Overflow用户
提问于 2018-06-01 12:24:11
回答 3查看 2.9K关注 0票数 6

QObject的工作线程在它们之前完成时,它们很容易变得无线程。当这种情况发生时,Qt不会释放它们的计时器ids,即使计时器不再处于活动状态。因此,将出现QBasicTimer::stop: Failed. Possibly trying to stop from a different thread警告。它主要是表面上的结果,但确实表明计时器id泄漏,因此有一个解决办法将是很好的。下面的示例触发问题:

代码语言:javascript
运行
复制
#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已经做的事情之外,不需要对计时器进行额外的跟踪。

EN

回答 3

Stack Overflow用户

发布于 2018-06-01 13:08:27

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

QObjectmoveToThread实现有两个部分:

  1. QEvent::ThreadChangemoveToThread传递给对象。QObject::event使用此事件来捕获和停用对象上活动的计时器。
  2. 目标线程中的事件循环将元调用传递给对象,_q_reregisterTimers在新线程中运行,定时器在新线程中重新激活。请注意,如果_q_reregisterTimers没有机会运行,它将不可避免地泄漏计时器list.

因此,我们需要:

  1. 捕获对象即将变得无线程的时刻,并将其移动到另一个线程,这样QMetaCallEvent to _q_reactivateTimers就不会在正确的线程中触发事件。

因此:

代码语言:javascript
运行
复制
// 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提供了足够的事件来进行这样的跟踪。

票数 3
EN

Stack Overflow用户

发布于 2018-06-01 13:06:09

代码语言:javascript
运行
复制
 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);});

...
票数 1
EN

Stack Overflow用户

发布于 2018-06-01 17:35:23

如何将对象移回主线程...

代码语言:javascript
运行
复制
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();
}
票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50636079

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档