Должны ли слушатели событий содержаться в слабых ссылках?

9

Обычно слушатели событий не должны переживать объект, который их зарегистрировал.

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

Существуют ли действительные случаи, когда слушатель должен пережить своего создателя?

Или, может быть, такая ситуация - ошибка, и ее нельзя допускать?

mrpyo
источник
Слабые ссылки обычно представлены экземплярами, и эти экземпляры также могут накапливаться до такой степени, что их нужно собирать как мусор. Так что это не бесплатный обед. Та же самая логика, которая убирает слабые ссылки, могла убрать сильные ссылки.
Фрэнк Хилман

Ответы:

7

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

Кроме того, если бы прослушиватели событий были слабо отосланы, вам бы пришлось хранить ссылки на них, даже если вы никогда не используете эту ссылку. Несоблюдение этого требования приведет к тому, что слушатель будет собираться в случайное время. Итак, мы получаем ошибку, которая

  • Легко создать по ошибке (все, что вам нужно сделать, это забыть сохранить объект в ссылочной переменной, которую вы никогда не будете использовать).
  • Трудно заметить (вы получите эту ошибку, только если GC соберет этот объект).
  • Трудно отладить (в сеансе отладки - который всегда работает как сеанс выпуска - вы столкнетесь с этой ошибкой, только если GC соберет объект).

И если избегание этой ошибки не является достаточно хорошим стимулом, вот еще несколько:

  1. Вам нужно будет придумать имя для каждого созданного вами слушателя.

  2. Некоторые языки используют статический анализ, который генерирует предупреждение, если у вас есть приватное поле, которое никогда не записывается или никогда не читается. Вам придется использовать механизм для переопределения этого.

  3. Слушатель событий что-то делает, и как только объект, имеющий сильную ссылку, будет собран, он перестанет что-то делать. Теперь у вас есть что-то, что влияет на состояние программы и зависит от GC - это означает, что GC влияет на конкретное состояние программы. И это плохо !

  4. Обработка слабых ссылок происходит медленнее, поскольку у вас есть другой уровень косвенности и вам нужно проверить, была ли ссылка собрана. Это не было бы проблемой, если бы было необходимо иметь прослушиватели событий в слабых ссылках - но это не так!

Идан Арье
источник
5

В общем, да, слабые ссылки должны быть использованы. Но сначала мы должны понять, что вы подразумеваете под «слушателями событий».

Callbacks

В некоторых стилях программирования, особенно в контексте асинхронных операций, принято представлять часть вычисления в виде обратного вызова, который выполняется для определенного события. Например, Promise[ 1 ] может иметь thenметод, который регистрирует обратный вызов после завершения предыдущего шага:

promise =
    Promise.new(async_task)                # - kick off a task
    .then(value => operation_on(value))    # - queue other operations
    .then(value => other_operation(value)) #   that get executed on completion
... # do other stuff in the meanwhile
# later:
result = promise.value # block for the result

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

Шаблон наблюдателя

В схеме наблюдателя субъект имеет список зависимых наблюдателей. Когда субъект входит в какое-то состояние, наблюдатели уведомляются согласно некоторому интерфейсу. Наблюдатели могут быть добавлены в тему и удалены из нее. Эти наблюдатели не существуют в семантическом вакууме, но ждут событий с какой-то целью.

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

Слабые ссылки исправляют эту утечку памяти, поскольку они позволяют наблюдателю собирать мусор. Когда субъект идет вокруг, чтобы уведомить всех наблюдателей и обнаруживает, что одна из слабых ссылок на наблюдателя пуста, эту ссылку можно безопасно удалить. Альтернативно, слабые ссылки могут быть реализованы таким образом, чтобы субъект мог зарегистрировать обратный вызов очистки, который удалит наблюдателя при сборе.

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

  • Делаем это вручную, но это подвержено ошибкам.

  • Использование чего-то похожего на try-with-resource в Java или usingв C #.

  • Детерминированное разрушение, например, через идиому RAII. Обратите внимание, что в языке с детерминированной сборкой мусора для этого может потребоваться слабая ссылка субъекта на наблюдателя для запуска деструктора.

Амон
источник