QSharedPointer уничтожается при излучении

Я новичок в Qt, и у меня есть некоторые проблемы с передачей QSharedPointer внутри сигналов. Я работаю с двумя потоками (пользовательский интерфейс и рабочий). Рабочий процесс отправляет сигналы пользовательскому интерфейсу, используя сигналы, содержащие QSharedPointer пользовательского QObject:

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 с окнами onFound с Qt::QueuedConnection, потому что они живут в разных потоках, и поэтому связь должна быть асинхронной.

Теперь я наблюдаю следующее поведение, когда я передаю последний QSharedPointer, ссылающийся на мой объект:

  • Сигнал moc передает ссылку на мой указатель на void* и архивирует его.
  • Функция возвращает, что приводит к уничтожению общего указателя и соответствующего объекта.

Это не то, что я ожидал, хотя это разумно. QSharedPointer вообще предназначен для передачи сигналов таким образом? И если да, то есть ли механизм сохранения ссылки, пока она стоит в очереди?

Я рассмотрел следующие решения, но я не совсем согласен ни с одним из них:

  1. Храните ссылку где-нибудь, которая сохраняет ссылку, пока она находится в очереди. Но где разумное место для этого и когда я должен отпустить это.
  2. Сделайте соединение Qt::DirectConnection, но тогда мне все равно придется как-то переключать поток (такая же ситуация, как и раньше)
  3. Ввести новый сигнал/слот с параметром std::function для передачи лямбда-функции для выполнения в целевом потоке и захвата копии моего общего указателя. (Это мое текущее решение, но оно не слишком элегантно, не так ли?)

У вас есть другие предложения или идеи?


person Myon    schedule 06.03.2018    source источник
comment
Я согласен с @KubaOber. Qt копирует аргументы из сигнала, чтобы доставить их в слот, поэтому я скептически отношусь к тому, что указанный объект удаляется, даже если QSharedPointer удален. Как узнать, что объект больше не жив?   -  person bnaecker    schedule 06.03.2018
comment
Если слот не вызывается, то почему вы ожидаете, что объект выживет? Это не будет. Но ваша проблема не связана с QSharedPointer, а связана с неправильной настройкой соединения. Покажите свой код. Снова и снова повторяется одно и то же: вы понятия не имеете, какой код дает сбой, поэтому показывать небольшие фрагменты с предлагаемыми решениями — пустая трата вашего и нашего времени. Сначала мы должны увидеть неудачу. Довольно часто при написании репродукторов вы сами видите, в чем была проблема, поскольку у вас должен быть код длиной в экран или два и легко понятный.   -  person Kuba hasn't forgotten Monica    schedule 06.03.2018


Ответы (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"
person Kuba hasn't forgotten Monica    schedule 06.03.2018
comment
К сожалению, слот не вызывается в моем сценарии. Единственное, что у меня сработало, это явно установить Qt::DirectConnection, но это не то, что я хочу. Я еще раз проверю, если зарегистрируюсь, мета-тип повлияет. - person Myon; 06.03.2018
comment
@Myon Запустите тестовый код из этого ответа. Это должно работать. Иди оттуда. Вы делаете что-то еще не так, тогда, вполне возможно, тоже. - person Kuba hasn't forgotten Monica; 06.03.2018
comment
@Myon Обратите внимание на первое утверждение в main(). Ваш репродуктор должен включать такое утверждение. Таким образом вы сообщаете нам, какая у вас версия Qt, и таким образом вы гарантируете, что ваш код действительно будет выполняться под ожидаемой версией Qt любым, кто захочет его запустить. - person Kuba hasn't forgotten Monica; 06.03.2018
comment
Спасибо за вашу помощь. Я не знаю точно, что вызвало проблему, но теперь это работает. - person Myon; 26.03.2018