Вам нужно удалить обработчик событий в деструкторе? - PullRequest
51 голосов
/ 12 июля 2011

Я использую некоторые UserControls, которые создаются и уничтожаются в моем приложении во время выполнения (создавая и закрывая подокна с этими элементами управления внутри).
Это WPF UserControl и наследуется от System.Windows.Controls.UserControl. Нет Dispose() метода, который я мог бы переопределить.
PPMM - это Singleton с тем же временем жизни, что и мое приложение.
Теперь в конструкторе моего (WPF) UserControl я добавляю обработчик событий:

public MyControl()
{
    InitializeComponent();

    // hook up to an event
    PPMM.FactorChanged += new ppmmEventHandler(PPMM_FactorChanged);
}

Я привык к удалению такого обработчика событий в деструкторе:

~MyControl()
{
    // hook off of the event
    PPMM.FactorChanged -= new ppmmEventHandler(PPMM_FactorChanged);
}

Сегодня я наткнулся на это и удивился:

1) Это необходимо? Или GC позаботится об этом?

2) Это вообще работает? Или мне придется хранить только что созданный ppmmEventHandler?

Я с нетерпением жду ваших ответов.

Ответы [ 9 ]

33 голосов
/ 12 июля 2011

Поскольку PPMM является долгоживущим объектом (синглтоном), то этот код не имеет особого смысла.

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

Таким образом, помещать что-либо в деструктор бессмысленно, например:

  1. Обработчик событий уже удален, поэтому объект получил право на сборку мусора
  2. Обработчик событий не удален, владелец объекта не подходит для сборки мусора, и, следовательно, финализатор никогда не будет вызван
  3. Оба объекта имеют право на сборку мусора, и в этом случае вам вообще не следует обращаться к этому другому объекту в финализаторе, поскольку вы не знаете его внутреннего состояния

Короче говоря, не делайте этого .

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

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

Что касается вопроса nbr. 2, который я принимаю как «Могу ли я отписаться от подобных событий», тогда да, вы можете. Единственный раз, когда вам нужно держаться за делегата, на которого вы подписались, это когда вы создаете делегата вокруг анонимного метода или лямбда-выражения. Когда вы создаете его вокруг существующего метода, он будет работать.


Редактировать : WPF. правильно, не видел этот тег. Извините, остальная часть моего ответа не имеет большого смысла для WPF, и, поскольку я не гуру WPF, я не могу сказать точно. Тем не менее, есть способ это исправить. Здесь, на SO, вполне законно вмешиваться в содержание другого ответа, если вы можете улучшить его. Поэтому, если кто-то знает, как правильно сделать это с помощью пользовательского контроля WPF, вы можете поднять весь первый раздел моего ответа и добавить соответствующие фрагменты WPF.

Редактировать : Позвольте мне также ответить на вопрос в комментарии здесь.

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

Правильный способ обработки этого пользовательского элемента управления, если он управляет своими собственными событиями, - это отцепить обработчики событий в методе Dispose.

(остальные удалены)

8 голосов
/ 30 июля 2014

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

Т.е. вы подключаетесь к событию в обработчике Loaded и отключаетесь в обработчике Unloaded.Конечно, это только вариант, если вашему элементу управления не нужно получать событие, пока оно не «загружено», и если вы можете правильно поддерживать множество циклов загрузки / выгрузки.

Преимущество использования Loaded /Unloaded events означает, что вам не нужно вручную размещать пользовательский элемент управления везде, где он используется.Однако вы должны знать, что событие Unloaded не запускается после начала закрытия приложения.Например, если ваш режим выключения OnMainWindowClose, события Unloaded для других окон не будут запускаться.Это обычно не проблема, хотя.Это просто означает, что вы не можете делать вещи надежно в Unloaded, который должен произойти до / во время завершения приложения.

7 голосов
/ 12 июля 2011

Во-первых, я бы сказал, что не используйте деструктор , но Dispose () , чтобы очистить ваши ресурсы.

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

С уважением.

2 голосов
/ 12 июля 2011

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

С другой стороны, используя финализатор, который НЕ является дескриптором. Это для этой плохой практики. Если вы хотите отменить регистрацию события, вы должны рассмотреть IDisposable.

2 голосов
/ 12 июля 2011

Является ли PPMM чем-то внешним с более длительным сроком службы, чем экземпляры MyControl?

Если это так, если PPMM_FactorChanged не является статическим методом, ppmmEventHandler будет сохранять ссылку на MyControl instance live - это означает, что экземпляр никогда не будет иметь права на сборку мусора, а финализатор никогда не сработает.

Вам не нужно хранить ppmmEventHandler для кода удаления.

2 голосов
/ 12 июля 2011

Если код дошел до деструктора, он больше не имеет значения.

Это потому, что он будет уничтожен, только если он больше не слушает какие-либо события.
Если он все еще слушалк событиям, он не был бы уничтожен.

1 голос
/ 18 июня 2015

В некоторых случаях отмена подписки на событие в Финализаторе / деструкторе может быть полезна, , если издатель событий гарантирует, что отписка является поточно-ориентированной . Для объекта, отписавшегося от его собственных событий, было бы бесполезно, но в качестве работоспособного шаблона можно было бы иметь общедоступный объект, содержащий ссылку на частный объект, который на самом деле «выполняет всю работу», и иметь этот частный объект подписаться на события. Если нет ссылки от частного объекта обратно на открытый объект, открытый объект станет пригодным для завершения, когда никто еще не заинтересован в нем; его финализатор сможет отменить подписку от имени частного объекта.

К сожалению, этот шаблон может работать только в том случае, если объекты, чьи события подписаны, гарантируют, что он может принимать запросы на отмену подписки из любого контекста потоков, а не только из контекста, где были подписаны события. Наличие .NET в качестве части контракта «события» требует, чтобы все методы отписки были поточно-ориентированными, не налагало бы серьезного бремени на реализацию, но по каким-то причинам MS не навязывала такое требование. Следовательно, даже если Финализатор / деструктор обнаруживает, что событие должно быть отписано как можно скорее, не существует стандартного механизма, с помощью которого он может это осуществить.

1 голос
/ 12 июля 2011

Обработчики событий сложны и могут легко скрыть утечку ресурсов. Как говорит Тигран. Используйте IDisposeable и забудьте о деструкторах. Я рекомендую оценить, правильно ли вы поняли. Просто взглянув на потребление памяти вашим приложением в диспетчере задач, вы узнаете, есть ли утечка, если вы слегка ее протестируете, загрузив и закрыв несколько тысяч окон.

0 голосов
/ 12 июля 2011

2) Это работает

1) У меня был случай (со службой обмена сообщениями в приложении), что обработчики событий для глобального объекта не были освобождены, и GC не смог собрать объект из-за этого. Я думаю, что это обычно редкое условие - используя профилировщик, такой как ANTS от red gate, вы можете легко выполнить профилирование памяти, если считаете, что это происходит с вами.

...