В каких случаях необходимо отстранение от событий?

Я не уверен, что полностью понимаю последствия присоединения к событиям в объектах.

Это мое текущее понимание, правильное или подробное:

<сильный>1. Присоединение к событиям локального класса не нужно отсоединять

Примеры:

this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);

public event EventHandler OnMyCustomEvent = delegate { };

Я предполагаю, что когда ваш объект удаляется или собирается мусор, функции освобождаются и автоматически отсоединяются от событий.

<сильный>2. Присоединение к объектам, которые вам больше не нужны (= null;), должно быть отсоединено от

Примеры: присоединение к событию Elapsed таймера, на которое вы отвечаете только один раз. Я предполагаю, что вам нужно сохранить таймер в локальной переменной, чтобы вы могли отсоединить событие Elapsed после того, как событие сработает. Таким образом, объявление таймера в области локального метода приведет к утечке:

System.Timers.Timer myDataTimer = new System.Timers.Timer(1000); myDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(myDataTimer_Elapsed);

<сильный>3. Присоединение к событиям в локальном объекте вашего класса не требует удаления?

Например, если у вас есть ObservableCollection, который вы создаете, отслеживаете и оставляете умирать. Если вы присоединитесь к событию CollectionChanged, используя локальную закрытую функцию, не будет ли эта функция освобождена, когда ваш класс будет собран мусором, что приведет к освобождению ObservableCollection?

Я уверен, что у меня есть места, где я перестал использовать объекты и не смог отсоединиться от события (например, пример таймера, который я сделал), поэтому я ищу более четкое объяснение того, как это работает.


person Will Eddins    schedule 21.04.2009    source источник


Ответы (3)


Я думаю, что вы делаете это более сложным, чем это должно быть. Вам просто нужно помнить две вещи:

  • Когда вы подписываетесь на событие, «владелец» события (издатель) обычно сохраняет ссылку на делегата, на которого вы подписаны.
  • Если вы используете метод экземпляра в качестве действия делегата, то делегат имеет ссылку на свой «целевой» объект.

Это означает, что если вы напишете:

publisher.SomeEvent += subscriber.SomeMethod;

Тогда subscriber не будет иметь права на сборку мусора до publisher, если вы не отмените подписку позже.

Обратите внимание, что во многих случаях subscriber — это просто this:

publisher.SomeEvent += myDataTimer_Elapsed;

эквивалентно:

publisher.SomeEvent += this.myDataTimer_Elapsed;

предполагая, что это метод экземпляра.

Обратной связи нет не существует только из-за подписки на событие — другими словами, подписчик не поддерживает издателя.

Кстати, дополнительную информацию см. в моей статье о событиях и делегатах.

person Jon Skeet    schedule 21.04.2009
comment
Вы совершенно правы, я слишком все усложняю. При поиске примеров почти все они демонстрировали отстраненность от события, что только навело меня на мысль, что подписчик может поддерживать жизнь издателя. - person Will Eddins; 21.04.2009
comment
Глупый вопрос. Означает ли это, что если издатель подписывается на событие подписчика, то ни один из двух не может/не будет собираться? - person Steven Evers; 22.04.2009
comment
Нет, это означает наличие циклической ссылки — как только не будет других корневых ссылок ни на одну из них, они обе будут иметь право на сборщик мусора. - person Jon Skeet; 22.04.2009

Остальные ссылки, предотвращающие сборку мусора, имеют еще один эффект, который может быть очевидным, но, тем не менее, еще не описанным в этой теме; прикрепленный обработчик событий также будет выполнен.

Я испытал это пару раз. Во-первых, у нас было приложение, которое постепенно становилось все медленнее и медленнее, чем дольше оно работало. Приложение создавало пользовательский интерфейс динамически, загружая пользовательские элементы управления. Контейнер заставлял пользовательские элементы управления подписываться на определенные события в среде, и одно из них не было отменено при «выгрузке» элементов управления.

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

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

person Fredrik Mörk    schedule 21.04.2009

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

public class A
{
    // ...
    public event EventHandler SomethingHappened;
}

public class B
{
    private void DoSomething() { /* ... */ } // instance method

    private void Attach(A obj)
    {
       obj.SomethingHappened += DoSomething();
    }
}

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

Вы могли бы столкнуться с тем же самым, если бы строка подписки на событие выглядела, конечно, так:

obj.SomethingHappened += someOtherObject.Whatever.DoSomething();

Теперь это someOtherObject, который находится на крючке и не может быть собран мусором.

person mqp    schedule 21.04.2009