我是Qt上的新手,但QSharedPointer在信号中传递时遇到了一些问题。我正在使用两个线程(UI和一个 worker )。 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>);
}

我将 worker found与Windows onFoundQt::QueuedConnection连接起来,因为它们生活在不同的线程中,因此通信必须是异步的。

现在,当我传递引用我的对象的最后一个QSharedPointer时,观察到以下行为:
  • 信号moc将引用转换为指向void*的指针并将其归档。
  • 函数返回结果将破坏共享指针和对应的对象。

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

    我考虑了以下解决方案,但我都不满意:
  • 在队列中保留引用的位置。但是这样做的合理位置在哪里,我什么时候应该放手。
  • 建立连接Qt::DirectConnection,但是我仍然必须以某种方式切换线程(与以前相同的情况)
  • 引入一个带有std::function参数的新信号/插槽,以传递要在目标线程中执行的lambda函数并捕获我的共享指针的副本。 (这是我当前的解决方案,但它并不完美,不是吗?)

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

    最佳答案

    信号返回不会破坏相应的对象。 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"
    

    10-08 11:39