【问题标题】:QSharedPointer gets destroyed within emitQSharedPointer 在发射中被破坏
【发布时间】:2023-04-02 03:32:01
【问题描述】:

我对 Qt 很陌生,遇到了QSharedPointer 在信号中传递的一些问题。我正在使用两个线程(UI 和一个工作线程)。 Worker 使用包含自定义 QObject 的 QSharedPointer 的信号向 UI 发送信号:

class MyObject : QObject {...}

class Window : public QWidget {
    Q_OBJECT
public slots:
    void onFound(QSharedPointer<MyObject>);
}

class Worker : public QObject {
    Q_OBJECT
public signals:
    void found(QSharedPointer<MyObject>);
}

我将工作人员 found 与窗口 onFoundQt::QueuedConnection 连接起来,因为它们位于不同的线程中,因此通信必须是异步的。

现在我观察到以下行为,当我通过最后一个 QSharedPointer 引用我的对象时:

  • 信号 moc 将对我的指针的引用转换为 void* 并将其归档。
  • 函数返回导致共享指针和相应对象被销毁。

这不是我所期望的——尽管它是合理的。 QSharedPointer 通常是否设计为通过这种方式传递信号?如果是这样,是否有一种机制可以在队列中保留引用?

我考虑了以下解决方案,但我对它们都不完全满意:

  1. 在某处保留引用,以便在排队时保留引用。但是在哪里是一个合理的地方,我应该什么时候放手。
  2. 建立连接Qt::DirectConnection,但我仍然必须以某种方式切换线程(情况与以前相同)
  3. 引入带有std::function 参数的新信号/槽,用于传递要在目标线程中执行的lambda 函数并捕获我的共享指针的副本。 (这是我目前的解决方案,但它并不优雅,不是吗?)

您还有其他建议或想法吗?

【问题讨论】:

  • 我同意@KubaOber。 Qt 复制信号中的参数以将它们传递到插槽,所以我怀疑即使 QSharedPointer 被删除,指向的对象也会被删除。你怎么知道对象不再活着?
  • 如果槽没有被调用,那么你为什么期望对象能够存活呢?它不会。但是您的问题与QSharedPointer 无关 - 与连接设置不正确有关。 显示您的代码。一遍又一遍:你不知道什么代码失败了,所以用建议的解决方案展示很少的 sn-ps 是浪费你的时间和我们的时间。我们必须首先看到失败。在编写复制器时,您经常会发现问题出在哪里——因为您应该拥有一两屏长且易于理解的代码。

标签: c++ qt qt5 qt-signals qsharedpointer


【解决方案1】:

信号返回不会破坏对应的对象。 QMetaObject::activate 调用复制共享指针。下面是send信号的实现:

// SIGNAL 0
void IO::send(const QSharedPointer<Unique> & _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

您可能正在经历一场竞赛:当发出信号的线程恢复执行时,目标线程已经接收到对象。因此,在发射线程中,对象已经消失了——因为到那时,它已经消失了。然而目标对象接收实例就好了。它工作正常。

下面的示例说明了它在单线程和多线程情况下都有效,然后通过确保目标线程始终赢得比赛来重现您的问题:

// https://github.com/KubaO/stackoverflown/tree/master/questions/shared-pointer-queued-49133331
#include <QtCore>

class Unique : public QObject {
   Q_OBJECT
   int const m_id = []{
      static QAtomicInteger<int> ctr;
      return ctr.fetchAndAddOrdered(1);
   }();
public:
   int id() const { return m_id; }
};

class IO : public QObject {
   Q_OBJECT
   int m_lastId = -1;
public:
   Q_SIGNAL void send(const QSharedPointer<Unique> &);
   Q_SLOT void receive(const QSharedPointer<Unique> & u) {
      m_lastId = u->id();
   }
   int lastId() const { return m_lastId; }
};

int main(int argc, char ** argv) {
   Q_ASSERT(QT_VERSION >= QT_VERSION_CHECK(5,9,0));
   QCoreApplication app{argc, argv};
   IO src, dst;
   QObject::connect(&src, &IO::send, &dst, &IO::receive, Qt::QueuedConnection);

   QSharedPointer<Unique> u;
   QWeakPointer<Unique> alive;
   int id = -1;

   // Single-threaded case
   alive = (u.reset(new Unique), u);
   id = u->id();
   Q_ASSERT(dst.lastId() != id); // the destination hasn't seen the object yet
   emit src.send(u);
   u.reset();
   Q_ASSERT(!u);                 // we gave up ownership of the object
   Q_ASSERT(dst.lastId() != id); // the destination mustn't seen the object yet
   Q_ASSERT(alive);              // the object must be still alive
   app.processEvents();
   Q_ASSERT(dst.lastId() == id); // the destination must have seen the object now
   Q_ASSERT(!alive);             // the object should have been destroyed by now

   // Multi-threaded setup
   struct Thread : QThread { ~Thread() { quit(); wait(); } } worker;
   worker.start();
   dst.moveToThread(&worker);
   QSemaphore s_src, s_dst;

   // This thread wins the race
   alive = (u.reset(new Unique), u);
   id = u->id();
   Q_ASSERT(dst.lastId() != id);
   QTimer::singleShot(0, &dst, [&]{ s_src.release(); s_dst.acquire(); });
                                 // stop the thread
   s_src.acquire();              // wait for thread to be stopped
   emit src.send(u);
   QTimer::singleShot(0, &dst, [&]{ s_src.release(); });
                                 // resume the main thread when done
   u.reset();
   Q_ASSERT(!u);
   Q_ASSERT(alive);              // we won the race: the object must be still alive
   s_dst.release();              // get the thread running
   s_src.acquire();              // wait for the thread to be done
   Q_ASSERT(dst.lastId() == id);
   Q_ASSERT(!alive);

   // The other thread wins the race
   alive = (u.reset(new Unique), u);
   id = u->id();
   Q_ASSERT(dst.lastId() != id);
   emit src.send(u);
   QTimer::singleShot(0, &dst, [&]{ s_src.release(); });
                                // resume the main thread when done
   u.reset();
   s_src.acquire();             // wait for worker thread to be done
   Q_ASSERT(!u);
   Q_ASSERT(!alive);            // we lost the race: the object must be gone
   Q_ASSERT(dst.lastId() == id); // yet the destination has received it!

   // Ensure the rendezvous logic didn't mess up
   Q_ASSERT(id == 2);
   Q_ASSERT(!s_src.available());
   Q_ASSERT(!s_dst.available());
}

#include "main.moc"

【讨论】:

  • 不幸的是,在我的场景中,插槽没有被调用。唯一对我有用的是明确设置 Qt::DirectConnection - 但这不是我想要的。我会仔细检查,如果注册元类型会产生影响。
  • @Myon 从这个答案运行测试代码。它应该可以工作。从那里去。那么,您很可能也做错了其他事情。
  • @Myon 注意main() 中的第一个断言。您的复制器应该包含这样的断言。这就是您告诉我们您的 Qt 版本是什么的方式,这就是您如何确保任何想要运行它的人在预期的 Qt 版本下实际执行您的代码。
  • 感谢您的帮助。我不知道究竟是什么导致了这个问题,但它现在可以工作了。
猜你喜欢
  • 2014-12-08
  • 2012-09-03
  • 1970-01-01
  • 2020-02-15
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 2012-09-02
  • 2018-10-07
相关资源
最近更新 更多