В каких случаях необходимо отрываться от событий? - PullRequest
16 голосов
/ 21 апреля 2009

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

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

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?

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

Ответы [ 3 ]

23 голосов
/ 21 апреля 2009

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

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

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

publisher.SomeEvent += subscriber.SomeMethod;

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

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

publisher.SomeEvent += myDataTimer_Elapsed;

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

publisher.SomeEvent += this.myDataTimer_Elapsed;

при условии, что это метод экземпляра.

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

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

3 голосов
/ 22 апреля 2009

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

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

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

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

1 голос
/ 21 апреля 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 находится на крючке и не может быть собран мусором.

...