QSharedPointer 在 emit 中被销毁
QSharedPointer gets destroyed within emit
我是 Qt 的新手,在信号内传递 QSharedPointer
时遇到了一些问题。我正在使用两个线程(UI 和一个工人)。工作人员使用包含自定义 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
与 windows onFound
与 Qt::QueuedConnection
连接起来,因为他们生活在不同的线程中,因此通信必须是异步的。
现在我观察到以下行为,当我传递最后一个 QSharedPointer
引用我的对象时:
- 信号 moc 将对我的指针的引用转换为
void*
并将其归档。
- 导致共享指针和相应对象被销毁的函数returns。
这不是我所期望的 - 虽然这是合理的。 QSharedPointer 通常设计为以这种方式传递信号吗?如果是这样,是否有一种机制可以在排队时保留引用?
我考虑了以下解决方案,但我对它们都不满意:
- 在某处保留引用,以便在排队时保留引用。但是哪里是一个合理的地方,我应该什么时候放手。
- 建立连接
Qt::DirectConnection
但我仍然必须以某种方式切换线程(与以前相同的情况)
- 引入一个新的 signal/slot 和
std::function
参数,用于传递要在目标线程中执行的 lambda 函数并捕获我的共享指针的副本。 (这是我目前的解决方案,但它不是很优雅,不是吗?)
您还有其他建议或想法吗?
信号return并没有销毁相应的对象。 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);
}
您可能正在经历一场竞赛:当发出信号的线程恢复执行时,目标线程已经接收到对象。因此,在发出线程中,对象消失了——因为到那时,它已经消失了。然而目标对象接收实例就好了。它工作正常。
下面的示例说明它在单一和 multi-threaded 情况下都有效,然后通过确保目标线程始终赢得比赛来重现您的问题:
// https://github.com/KubaO/Whosebugn/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 的新手,在信号内传递 QSharedPointer
时遇到了一些问题。我正在使用两个线程(UI 和一个工人)。工作人员使用包含自定义 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
与 windows onFound
与 Qt::QueuedConnection
连接起来,因为他们生活在不同的线程中,因此通信必须是异步的。
现在我观察到以下行为,当我传递最后一个 QSharedPointer
引用我的对象时:
- 信号 moc 将对我的指针的引用转换为
void*
并将其归档。 - 导致共享指针和相应对象被销毁的函数returns。
这不是我所期望的 - 虽然这是合理的。 QSharedPointer 通常设计为以这种方式传递信号吗?如果是这样,是否有一种机制可以在排队时保留引用?
我考虑了以下解决方案,但我对它们都不满意:
- 在某处保留引用,以便在排队时保留引用。但是哪里是一个合理的地方,我应该什么时候放手。
- 建立连接
Qt::DirectConnection
但我仍然必须以某种方式切换线程(与以前相同的情况) - 引入一个新的 signal/slot 和
std::function
参数,用于传递要在目标线程中执行的 lambda 函数并捕获我的共享指针的副本。 (这是我目前的解决方案,但它不是很优雅,不是吗?)
您还有其他建议或想法吗?
信号return并没有销毁相应的对象。 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);
}
您可能正在经历一场竞赛:当发出信号的线程恢复执行时,目标线程已经接收到对象。因此,在发出线程中,对象消失了——因为到那时,它已经消失了。然而目标对象接收实例就好了。它工作正常。
下面的示例说明它在单一和 multi-threaded 情况下都有效,然后通过确保目标线程始终赢得比赛来重现您的问题:
// https://github.com/KubaO/Whosebugn/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"