Удаление силы слота в boost:: сигналы2

Я обнаружил, что boost:: signals2 использует своего рода ленивое удаление подключенных слотов, что затрудняет использование соединений как то, что управляет временем жизни объектов. Я ищу способ принудительного удаления слотов непосредственно при отключении. Любые идеи о том, как обойти проблему, разрабатывая мой код по-разному, также оценены!

Это мой сценарий: у меня есть класс Command, ответственный за выполнение чего-то, что требует времени асинхронно, выглядя что-то вроде этого (упрощенного):

class ActualWorker {
public:
    boost::signals2<void ()> OnWorkComplete;
};

class Command : boost::enable_shared_from_this<Command> {
public:
    ...

    void Execute() {
        m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind(&Command::Handle_OnWorkComplete, shared_from_this());

        // launch asynchronous work here and return
    }

    boost::signals2<void ()> OnComplete;

private:
    void Handle_OnWorkComplete() {
        // get a shared_ptr to ourselves to make sure that we live through
        // this function but don't keep ourselves alive if an exception occurs.
        shared_ptr<Command> me = shared_from_this();

        // Disconnect from the signal, ideally deleting the slot object
        m_WorkerConnection.disconnect();

        OnComplete();

        // the shared_ptr now goes out of scope, ideally deleting this
    }

    ActualWorker m_MyWorker;
    boost::signals2::connection m_WorkerConnection;
};

Класс вызывается примерно так:

...
boost::shared_ptr<Command> cmd(new Command);
cmd->OnComplete.connect( foo );
cmd->Execute();
// now go do something else, forget all about the cmd variable etcetera.

класс Command сохраняет себя в живых, получая shared_ptr для себя, который привязан к сигналу ActualWorker с использованием boost:: bind.

Когда рабочий завершается, вызывается обработчик в команде. Теперь, поскольку я хотел бы, чтобы объект Command был уничтожен, я отсоединяюсь от сигнала, как это видно из кода выше. Проблема в том, что фактический объект слота не удаляется при отключении, он помечен как недействительный, а затем удален позже. Это, в свою очередь, зависеть от сигнала, чтобы он снова срабатывал, чего он не делает в моем случае, что приводит к тому, что слот никогда не истекает. Таким образом, объект boost:: bind никогда не выходит за пределы области видимости, сохраняя shared_ptr для моего объекта, который никогда не будет удален.

Я могу обойти это, связываясь с помощью этого указателя вместо shared_ptr, а затем сохраняя свой объект живым, используя член shared_ptr, который затем я освобождаю в функции обработчика, но это немного заставляет дизайн чувствовать себя слишком сложным. Есть ли способ заставить сигналы2 удалить слот при отключении? Или я могу сделать что-то еще, чтобы упростить дизайн?

Любые комментарии оценены!

Ответ 1

Я закончил свою собственную (подмножество) реализацию сигнала, главным требованием было то, что слот должен быть уничтожен вызовом connection:: disconnect().

Реализация идет по линиям сигнала, хранящего все слоты в карте от указателя реализации слота к shared_ptr для реализации слота вместо списка/вектора, тем самым обеспечивая быстрый доступ к отдельным слотам без необходимости повторения по всем слотам, Реализация слота в моем случае в основном является функцией boost::.

Соединения имеют weak_ptr для внутреннего класса реализации для сигнала и weak_ptr для типа реализации слота, чтобы позволить сигналу выйти из области действия и использовать указатель слота в качестве ключа в карте сигналов, а также указание о том, является ли соединение еще активным (не может использовать необработанный указатель, который может быть повторно использован).

Когда вызывается вызов disconnect, оба этих слабых указателя преобразуются в shared_ptrs, и если оба из них преуспевают, для реализации сигнала предлагается отключить слот, заданный указателем. Это делается путем простого удаления его с карты.

Карта защищена мьютексом для многопоточного использования. Чтобы предотвратить взаимоблокировки, мьютексы не удерживаются во время вызова слотов, однако это означает, что слот может быть отсоединен от другого потока непосредственно перед вызовом сигнала. Это также имеет место с регулярными boost:: signals2, и в обоих этих сценариях нужно иметь возможность обрабатывать обратный вызов из сигнала даже после того, как он отключился.

Чтобы упростить код при запуске сигнала, я заставляю все разъемы отключать во время этого. Это отличается от boost:: signals2, который выполняет копирование списка слотов перед их вызовом, чтобы обрабатывать разъединения/соединения при подаче сигнала.

Это хорошо работает для моего сценария, где интересный сигнал запускается очень редко (и в этом случае только один раз), но существует много короткоживущих соединений, которые в противном случае используют большую память даже при использовании трюк, обозначенный в вопросе.

В других сценариях я смог заменить использование сигнала только функцией boost:: (таким образом, чтобы было возможно единственное соединение) или просто придерживаться обходного пути в вопросе, где сам слушатель управляет своим временем жизни.

Ответ 2

boost::signals2 выполняет очистку слотов во время соединения/вызова.

Итак, если все слоты отсоединяются от сигнала, второй раз вызов сигнала не вызовет ничего, кроме как он должен очистить слоты.

Чтобы ответить на ваш комментарий, да, вызов сигнала снова небезопасен, если есть другие подключенные слоты, так как они будут снова вызваны. В этом случае я предлагаю вам пойти другим путем и подключить фиктивный слот, а затем отключить его, когда будет задействован ваш "настоящий" слот. Подключение другого слота очистит устаревшие соединения, поэтому ваш слот должен быть отпущен.

Просто убедитесь, что вы не храните ссылки, требующие освобождения в фиктивном слоте, или вы вернулись туда, где вы начали.

Ответ 3

Это невероятно раздражающий аспект boost:: signals2.

Подход, который я принял для его решения, заключается в сохранении сигнала в scoped_ptr, и когда я хочу принудительно отключить все слоты, я удаляю сигнал. Это работает только в тех случаях, когда вы хотите принудительно отключить все соединения с сигналом.

Ответ 4

Является ли поведение более строгим с помощью scoped_connection?

Итак, вместо:

void Execute() {
    m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this());

    // launch asynchronous work here and return
}

...

boost::signals2::connection m_WorkerConnection;

Вместо этого:

void Execute() {
    boost::signals2::scoped_connection m_WorkerConnection
        (m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this()));

    // launch asynchronous work here and return
}   // connection falls out of scope

(копируется из boost::signals2::connection)

Я не использовал каких-либо сигналов, поэтому это скорее предположение, чем что-либо еще, но после Execute() вам не нужно disconnect(), так как scoped_connection обрабатывает его для вас. Это скорее "упростить дизайн", чем фактически решить вашу проблему. Но это может означать, что вы можете Execute(), а затем сразу ~Command() (или delete shared_ptr).

Надеюсь, что это поможет.

EDIT: И Execute(), а затем ~Command() я, очевидно, подразумеваю извне вашего объекта Command. Когда вы создаете команду для ее выполнения, вы должны иметь возможность сказать:

cmd->Execute();
delete cmd;

Или аналогичный.

Ответ 5

Я наткнулся на ту же проблему, и я действительно пропустил какую-то явную очистку в API.

В моем сценарии я выгружаю некоторую подключаемую DLL-версию, и я должен убедиться, что нет никаких оборванных объектов (слотов), которые относятся к коду (vftables или вообще), живущему в выгруженной dll. Простое разъединение слотов не работало из-за ленивых файлов удаления.

Моим первым решением было обертка сигнала, которая немного улучшает код разъединения:

template <typename Signature>
struct MySignal
{
  // ...

  template <typename Slot>
  void disconnect (Slot&& s)
  {
    mPrivate.disconnect (forward (s));
    // connect/disconnect dummy slot to force cleanup of s
    mPrivate.connect (&MySignal::foo);
    mPrivate.disconnect (&MySignal::foo);
  }

private:
  // dummy slot function with matching signature
  // ... foo (...)

private:
  ::boost::signals2::signal<Signature> mPrivate;
};

К сожалению, это не сработало, потому что connect() выполняет очистку some. Это не гарантирует очистку всех несвязанных слотов. Вызов сигнала, с другой стороны, выполняет полную очистку, но фиктивный вызов также будет неприемлемым поведением (как уже упоминалось другими). ​​

В отсутствие альтернатив я закончил исправление исходного класса signal (Изменить:), я бы по достоинству оценил встроенное решение. Этот патч был моим последним средством). Мой патч составляет около 10 строк кода и добавляет общедоступный метод cleanup_connections() к signal. Моя оболочка сигнала вызывает очистку в конце методов отключения. Этот подход разрешил мои проблемы, и до сих пор я не сталкивался с какими-либо проблемами с производительностью.

Изменить: Вот мой патч для повышения 1.5.3

Index: signals2/detail/signal_template.hpp
===================================================================
--- signals2/detail/signal_template.hpp
+++ signals2/detail/signal_template.hpp
@@ -220,6 +220,15 @@
           typedef mpl::bool_<(is_convertible<T, group_type>::value)> is_group;
           do_disconnect(slot, is_group());
         }
+        void cleanup_connections () const
+        {
+          unique_lock<mutex_type> list_lock(_mutex);
+          if(_shared_state.unique() == false)
+          {
+            _shared_state.reset(new invocation_state(*_shared_state, _shared_state->connection_bodies()));
+          }
+          nolock_cleanup_connections_from(false, _shared_state->connection_bodies().begin());
+        }
         // emit signal
         result_type operator ()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS))
         {
@@ -690,6 +699,10 @@
       {
         (*_pimpl).disconnect(slot);
       }
+      void cleanup_connections ()
+      {
+        (*_pimpl).cleanup_connections();
+      }
       result_type operator ()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS))
       {
         return (*_pimpl)(BOOST_SIGNALS2_SIGNATURE_ARG_NAMES(BOOST_SIGNALS2_NUM_ARGS));