В каких случаях требуется отстранение от необходимых событий?
Я не уверен, полностью ли я понимаю последствия прикрепления к событиям в объектах.
Это мое настоящее понимание, правильное или подробное:
1. Привязка к локальным событиям класса не требуется отсоединять
Примеры:
this.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
public event EventHandler OnMyCustomEvent = delegate { };
Я предполагаю, что когда ваш объект удаляется или мусор собирается, функции освобождаются и автоматически отделяются от событий.
2. Прикрепление к объектам, которые вам больше не нужны (= null;), необходимо отделить от
Примеры:
Присоединение к таймеру Истекшее событие, на которое вы реагируете только один раз. Я предполагаю, что вам нужно хранить таймер в локальной переменной, чтобы вы могли отключить событие "Истекшее" после возникновения события. Таким образом, объявление таймера в области локального метода, например, приведет к утечке:
System.Timers.Timer myDataTimer = new System.Timers.Timer(1000);
myDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(myDataTimer_Elapsed);
3. Прикрепление к событиям в локальном объекте к вашему классу не требует удаления?
Например, если у вас есть ObservableCollection, вы создаете, контролируете и даете умереть. Если вы присоединяетесь к событию CollectionChanged с использованием локальной, частной функции, не будет ли эта функция освобождаться, когда ваш класс собирает мусор, заставляя ObservableCollection также освобождаться?
Я уверен, что у меня есть места, где я прекратил использовать объекты и не смог отсоединиться от события (например, пример таймера, который я сделал), поэтому я ищу более четкое объяснение того, как это работает.
Ответы
Ответ 1
Я думаю, вы делаете это более сложным, чем нужно. Вам просто нужно запомнить две вещи:
- Когда вы подписываетесь на событие, событие "владелец" (издатель) обычно содержит ссылку на делегата, с которым вы подписаны.
- Если вы используете метод экземпляра в качестве действия делегата, то делегат имеет ссылку на его "целевой" объект.
Это означает, что если вы пишете:
publisher.SomeEvent += subscriber.SomeMethod;
Тогда subscriber
не будет иметь права на сбор мусора до publisher
, если вы не отмените подписку позже.
Обратите внимание, что во многих случаях subscriber
является просто this
:
publisher.SomeEvent += myDataTimer_Elapsed;
эквивалентно:
publisher.SomeEvent += this.myDataTimer_Elapsed;
считая его методом экземпляра.
Нет обратной связи только из-за подписки на события - другими словами, абонент не сохраняет живого издателя.
См. мою статью о событиях и делегатах для получения дополнительной информации, кстати.
Ответ 2
Остальные ссылки, препятствующие сборке мусора, имеют еще один эффект, который может быть очевиден, но, тем не менее, еще не указан в этой теме; подключенный обработчик событий также будет исключен.
Я испытал это несколько раз. Один из них заключался в том, что у нас было приложение, которое постепенно становилось медленнее и медленнее, чем дольше работало. Приложение создало пользовательский интерфейс динамически, загружая пользовательские элементы управления. Контейнер заставил пользовательские элементы управления подписываться на определенные события в среде, и одна из них не была отписана, когда элементы управления были "разгружены".
Через некоторое время это привело к тому, что большое количество прослушивателей событий выполнялось каждый раз, когда возникало конкретное событие. Это, конечно, может привести к серьезным условиям гонки, когда большое количество "спящих" случаев внезапно пробуждается и пытается действовать на одном и том же входе.
Короче говоря; если вы пишете код, чтобы подключить прослушиватель событий; убедитесь, что вы отпустите, как только это не понадобится. Я почти осмеливаюсь пообещать, что в какой-то момент в будущем он спасет вас от хотя бы одной головной боли.
Ответ 3
Соответствующий случай, когда вы должны отказаться от подписки на событие, выглядит следующим образом:
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
, что на крючке и не может быть собрано мусор.