Я только что понял, прочитав некоторые вопросы и ответы по StackOverflow, что добавление обработчиков событий, использующих +=
в C # (или, я полагаю, в других языках .net), может вызвать общие утечки памяти ...
В прошлом я много раз использовал подобные обработчики событий и никогда не осознавал, что они могут вызвать или вызвали утечки памяти в моих приложениях.
Как это работает (имеется в виду, почему это на самом деле вызывает утечку памяти)?
Как я могу решить эту проблему? -=
Достаточно ли использовать один и тот же обработчик событий?
Существуют ли общие шаблоны проектирования или лучшие практики для обработки подобных ситуаций?
Пример: как я должен обрабатывать приложение, которое имеет много разных потоков, используя много разных обработчиков событий, чтобы вызвать несколько событий в пользовательском интерфейсе?
Есть ли хорошие и простые способы эффективно контролировать это в уже построенном большом приложении?
Да,
-=
достаточно, однако, может быть довольно сложно отслеживать каждое назначенное событие, когда-либо. (подробнее см. пост Джона). Что касается шаблона проектирования, взгляните на шаблон слабых событий .источник
IDisposable
и отписываюсь от события.Я объяснил эту путаницу в блоге по адресу https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Я постараюсь обобщить это здесь, чтобы вы могли иметь четкое представление.
Справочное обозначение «Потребность»:
Прежде всего, вы должны понимать, что если объект A содержит ссылку на объект B, то это будет означать, что объект A нуждается в объекте B, чтобы функционировать, верно? Таким образом, сборщик мусора не будет собирать объект B, пока объект A находится в памяти.
Я думаю, что эта часть должна быть очевидной для разработчика.
+ = Означает, что вставка ссылки правого объекта на левый объект:
Но путаница возникает из-за оператора C # + =. Этот оператор четко не сообщает разработчику, что правая часть этого оператора фактически вводит ссылку на левый объект.
Таким образом, объект A думает, что ему нужен объект B, хотя, с вашей точки зрения, объект A не должен заботиться о том, живет ли объект B или нет. Поскольку объект A считает, что объект B необходим, объект A защищает объект B от сборщика мусора, пока объект A жив. Но если вы не хотите, чтобы эта защита предоставлялась объекту подписчика на событие, то вы можете сказать, что произошла утечка памяти.
Вы можете избежать такой утечки, отсоединив обработчик событий.
Как принять решение?
Но во всей вашей кодовой базе есть много событий и обработчиков событий. Означает ли это, что вам нужно постоянно отсоединять обработчики событий? Ответ - нет. Если бы вам пришлось это сделать, ваша кодовая база будет очень уродливой с многословной.
Вы можете использовать простую блок-схему, чтобы определить, требуется ли отсоединение обработчика событий.
В большинстве случаев вы можете обнаружить, что объект подписчика на событие так же важен, как и объект публикации события, и предполагается, что оба они живут одновременно.
Пример сценария, в котором вам не нужно беспокоиться
Например, событие нажатия кнопки окна.
Здесь издателем события является кнопка, а подписчиком события - главное окно. Применяя эту блок-схему, задайте вопрос, должно ли основное окно (подписчик события) быть мертвым перед кнопкой (издателем события)? Очевидно, нет. Верно? Это даже не имеет смысла. Тогда зачем беспокоиться об отключении обработчика события click?
Пример, когда отсоединение обработчика события является ОБЯЗАТЕЛЬНЫМ.
Я приведу один пример, когда объект подписчика должен быть мертвым перед объектом издателя. Скажем, ваше MainWindow публикует событие с именем SomethingHappened, и вы показываете дочернее окно из главного окна нажатием кнопки. Дочернее окно подписывается на это событие главного окна.
И дочернее окно подписывается на событие Главного окна.
Из этого кода мы можем ясно понять, что в главном окне есть кнопка. Нажав на эту кнопку, вы увидите дочернее окно. Дочернее окно слушает событие из главного окна. Сделав что-то, пользователь закрывает дочернее окно.
Теперь, в соответствии с блок-схемой, которую я предоставил, если вы зададите вопрос «Предполагается, что дочернее окно (подписчик события) должно быть мертвым до публикации события (главное окно)? Ответ должен быть ДА. Верно? Итак, отсоедините обработчик события». Я обычно делаю это из события Unloaded окна.
Практическое правило. Если ваше представление (т. Е. WPF, WinForm, UWP, форма Xamarin и т. Д.) Подписывается на событие ViewModel, всегда не забывайте отключать обработчик события. Потому что ViewModel обычно живет дольше, чем представление. Таким образом, если ViewModel не уничтожен, любое представление, подписанное на событие этой ViewModel, останется в памяти, что не очень хорошо.
Доказательство концепции с использованием профилировщика памяти.
Будет не очень весело, если мы не сможем проверить концепцию с помощью профилировщика памяти. В этом эксперименте я использовал профилировщик JetBrain dotMemory.
Сначала я запустил MainWindow, которое выглядит так:
Затем я сделал снимок памяти. Затем я нажал кнопку 3 раза . Появились три детских окна. Я закрыл все эти дочерние окна и нажал кнопку Force GC в профилировщике dotMemory, чтобы убедиться, что вызывается сборщик мусора. Затем я сделал еще один снимок памяти и сравнил его. Вот! наш страх был правдой. Детское окно не было собрано сборщиком мусора даже после его закрытия. Не только это, но и количество просочившихся объектов для объекта ChildWindow также отображается как « 3 » (я нажал кнопку 3 раза, чтобы показать 3 дочерних окна).
Хорошо, тогда я отключил обработчик событий, как показано ниже.
Затем я выполнил те же действия и проверил профилировщик памяти. На этот раз вау! нет больше утечки памяти.
источник
Событие - это действительно связанный список обработчиков событий
Когда вы делаете + = новый EventHandler для события, на самом деле не имеет значения, была ли эта конкретная функция ранее добавлена в качестве прослушивателя, она будет добавлена один раз за + =.
Когда событие вызывается, оно проходит по связанному списку, элемент за элементом и вызывает все методы (обработчики событий), добавленные в этот список, поэтому обработчики событий по-прежнему вызываются, даже если страницы больше не работают, пока они живы (укоренены), и они будут живы, пока они подключены. Поэтому они будут вызываться до тех пор, пока обработчик событий не отсоединится с - = new EventHandler.
Посмотреть здесь
и MSDN ЗДЕСЬ
источник