Предотвращение утечек памяти с помощью прикрепленных действий

Я создал «присоединенное поведение» в своем приложении WPF, которое позволяет мне обрабатывать нажатие клавиши Enter и переходить к следующему элементу управления. Я называю его EnterKeyTraversal.IsEnabled, и вы можете увидеть код в моем блоге здесь.

Моя главная проблема сейчас заключается в том, что у меня может быть утечка памяти, поскольку я обрабатываю событие PreviewKeyDown в UIElements и никогда явно не "отцепляю" событие.

Как лучше всего предотвратить эту утечку (если она действительно есть)? Должен ли я вести список элементов, которыми я управляю, и отключать событие PreviewKeyDown в событии Application.Exit? Кто-нибудь добился успеха с присоединенным поведением в своих собственных приложениях WPF и придумал элегантное решение для управления памятью?


person Matt Hamilton    schedule 18.08.2008    source источник


Ответы (11)


Я не согласен ДэнниСмурф

Некоторые объекты макета WPF могут забивать вашу память и сильно замедлять работу вашего приложения, если они не удаляются сборщиком мусора. Итак, я считаю, что выбор слов правильный, вы пропускаете память к объектам, которые вы больше не используете. Вы ожидаете, что элементы будут удалены сборщиком мусора, но это не так, потому что где-то есть ссылка (в данном случае из обработчика событий).

Теперь реальный ответ :)

Я советую вам прочитать эту статью о производительности WPF на MSDN

Неудаление обработчиков событий на объектах может оставить объекты живыми

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

Они советуют вам изучить шаблон Weak Event.

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

Надеюсь это поможет!

person Arcturus    schedule 18.08.2008
comment
Я голосую против, потому что ответ не имеет ничего общего с фактически заданным вопросом. Все в целом знают о ямах утечки памяти и ее причинах, особенно из-за обработчиков событий, как вы упомянули. Что @Matt здесь хотел знать, так это то, как безопасно обрабатывать обработчики событий при использовании внутренних подключенных поведений. Я опубликую ответ на это в ближайшее время. - person Saraf Talukder; 04.06.2015

Помимо философских дебатов, глядя на сообщение в блоге ОП, я не вижу здесь никакой утечки:

ue.PreviewKeyDown += ue_PreviewKeyDown;

Жесткая ссылка на ue_PreviewKeyDown хранится в ue.PreviewKeyDown.

ue_PreviewKeyDown — это метод STATIC и не может быть GCed.

Жесткая ссылка на ue не сохраняется, поэтому ничто не мешает ему быть GCed.

Итак... Где утечка?

person John Fenton    schedule 29.05.2011
comment
Это распространенное недоразумение. ue.PreviewKeyDown += ue_PreviewKeyDown содержит строгую ссылку на ue, а поскольку us_PreviewKeyDown является статическим, ue никогда не будет собран. - person Daniel Bişar; 22.03.2012
comment
@SACO, ты можешь это объяснить? Где хранится сильная ссылка на ue? Насколько я вижу, Джон полностью прав, в исходном примере абсолютно нет утечки памяти. ue.PreviewKeyDown -= ue_PreviewKeyDown не нужно. - person Golvellius; 30.01.2015
comment
@Golvellius Я опубликовал ответ, который должен объяснить мою точку зрения. Я на самом деле проверил это сейчас и обнаружил, что утечки не будет, если ue_PreviewKeyDown является статическим. - person Daniel Bişar; 30.01.2015

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

Microsoft даже признает, что это утечка памяти:

Зачем внедрять шаблон WeakEvent?

Прослушивание событий может привести к утечке памяти. Типичный метод прослушивания события заключается в использовании специфичного для языка синтаксиса, который прикрепляет обработчик к событию в источнике. Например, в C# используется следующий синтаксис: source.SomeEvent += new SomeEventHandler(MyEventHandler).

Этот метод создает сильную ссылку от источника события к прослушивателю событий. Обычно присоединение обработчика событий для прослушивателя приводит к тому, что у прослушивателя есть время жизни объекта, которое зависит от времени жизни объекта для источника (если только обработчик событий не удален явно). Но в определенных обстоятельствах вам может понадобиться, чтобы время жизни объекта прослушивателя контролировалось только другими факторами, например тем, принадлежит ли он в настоящее время визуальному дереву приложения, а не временем жизни источника. Всякий раз, когда время жизни исходного объекта выходит за пределы времени жизни объекта прослушивателя, обычный шаблон событий приводит к утечке памяти: прослушиватель сохраняется дольше, чем предполагалось.

Мы используем WPF для клиентского приложения с большими окнами ToolWindows, которые можно перетаскивать, со всеми изящными вещами, и все они совместимы с XBAP. Но у нас была такая же проблема с некоторыми окнами ToolWindows, которые не были собраны мусором. к тому факту, что он все еще зависел от прослушивателей событий. Теперь это может не быть проблемой, когда вы закрываете окно и закрываете приложение. Но если вы создаете очень большие ToolWindows с большим количеством команд, и все эти команды переоцениваются снова и снова, и люди должны использовать ваше приложение в течение всего дня... Я могу вам сказать... это действительно засоряет вашу память и время отклика вашего приложения..

Кроме того, мне гораздо проще объяснить своему менеджеру, что у нас утечка памяти, чем объяснять ему, что некоторые объекты не собираются мусором из-за каких-то событий, которые нужно очистить ;)

person Arcturus    schedule 18.08.2008

@Nick Да, проблема с присоединенными поведениями в том, что по определению они не находятся в том же объекте, что и элементы, события которых вы обрабатываете.

Я думаю, что ответ каким-то образом лежит в использовании WeakReference, но я не видел простых примеров кода, чтобы объяснить это мне. :)

person Matt Hamilton    schedule 18.08.2008

Чтобы объяснить мой комментарий к сообщению Джона Фентона, вот мой ответ. Давайте посмотрим на следующий пример:

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();

        a.Clicked += b.HandleClicked;
        //a.Clicked += B.StaticHandleClicked;
        //A.StaticClicked += b.HandleClicked;

        var weakA = new WeakReference(a);
        var weakB = new WeakReference(b);

        a = null;
        //b = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine("a is alive: " + weakA.IsAlive);
        Console.WriteLine("b is alive: " + weakB.IsAlive);
        Console.ReadKey();
    }


}

class A
{
    public event EventHandler Clicked;
    public static event EventHandler StaticClicked;
}

class B
{
    public void HandleClicked(object sender, EventArgs e)
    {
    }

    public static void StaticHandleClicked(object sender, EventArgs e)
    {
    }
}

Если у вас есть

a.Clicked += b.HandleClicked;

и установите только b равным нулю, обе ссылки weakA и weakB останутся в живых! Если вы установите значение null только для a, b останется активным, но не a (что доказывает, что Джон Фентон ошибается, утверждая, что в поставщике событий хранится жесткая ссылка — в данном случае a).

Это привело меня к НЕПРАВИЛЬНОМУ выводу, что

a.Clicked += B.StaticHandleClicked;

приведет к утечке, потому что экземпляр a будет сохранен статическим обработчиком. Это не так (проверьте мою программу). В случае статического обработчика событий или событий все наоборот. Если вы пишете

A.StaticClicked += b.HandleClicked;

ссылка будет сохранена на b.

person Daniel Bişar    schedule 30.01.2015
comment
Спасибо, что потратили время на ответ на мой комментарий, НО: Джон Фентон не ошибается, заявляя, что жесткая ссылка хранится в поставщике событий. Установив a значение null, вы в основном удаляете жесткую ссылку, потому что объект памяти, на который указывает a, впоследствии удаляется мусором. И a.Clicked += B.StaticHandleClicked; - это именно ситуация с ОП - это никогда не может вызвать утечку памяти, отсюда и вопрос Джона Фентона Итак... Где утечка?. Только наоборот, как вы указали, но это не та ситуация, в которой находится ОП. - person Golvellius; 30.01.2015

Думали ли вы о реализации «Шаблона слабых событий» вместо обычных событий?

  1. Шаблон слабых событий в WPF
  2. Шаблоны слабых событий (MSDN)
person Patrik Svensson    schedule 18.08.2008

Убедитесь, что элементы, ссылающиеся на события, находятся в объекте, на который они ссылаются, например текстовые поля в элементе управления формы. Или если это нельзя предотвратить. Создайте статическое событие в глобальном вспомогательном классе, а затем отслеживайте события в глобальном вспомогательном классе. Если эти два шага не могут быть выполнены, попробуйте использовать WeakReference, они обычно идеально подходят для таких ситуаций, но требуют накладных расходов.

person Nick Berardi    schedule 18.08.2008

Я только что прочитал ваш пост в блоге и думаю, что вы получили несколько вводящих в заблуждение советов, Мэтт. Если здесь есть фактическая утечка памяти, то это ошибка в .NET Framework, а не то, что вы обязательно сможете исправить в своем коде.

Я думаю, что вы (и постер в своем блоге) на самом деле говорите здесь не об утечке, а о продолжающемся потреблении памяти. Это не то же самое. Чтобы было ясно, утечка памяти — это память, которая зарезервирована программой, затем заброшена (т. е. указатель остается висящим) и которая впоследствии не может быть освобождена. Поскольку управление памятью осуществляется в .NET, это теоретически невозможно. Однако программа может резервировать постоянно увеличивающийся объем памяти, не позволяя ссылкам на нее выходить за пределы области видимости (и получать право на сборку мусора); однако эта память не просочилась. Сборщик мусора вернет его системе после выхода из вашей программы.

Так. Чтобы ответить на ваш вопрос, я не думаю, что у вас действительно есть проблема. У вас, конечно, нет утечки памяти, и, исходя из вашего кода, я не думаю, что вам нужно беспокоиться о потреблении памяти. Пока вы убедитесь, что вы не назначаете этот обработчик событий повторно, даже не отменяя его назначения (т. е. что вы либо устанавливаете его только один раз, либо удаляете его ровно один раз при каждом назначении), что вы, кажется, делаете, ваш код должен быть в порядке.

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

person TheSmurf    schedule 18.08.2008

@Арктур:

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

Это совершенно очевидно, и я не соглашусь. Однако:

... вы пропускаете память к объекту, который вы больше не используете ... потому что на них есть ссылка.

«память выделяется программе, и эта программа впоследствии теряет возможность доступа к ней из-за недостатков логики программы» (Википедия, «Утечка памяти»)

Если есть активная ссылка на объект, к которому ваша программа может получить доступ, то по определению это не утечка памяти. Утечка означает, что объект больше не доступен (для вас или для ОС/фреймворка) и не будет освобожден на время существования текущего сеанса операционной системы. Это не тот случай здесь.

(Извините, что я семантический нацист... может быть, я немного старомоден, но утечка имеет очень специфическое значение. В наши дни люди склонны использовать «утечку памяти» для обозначения всего, что потребляет на 2 КБ памяти больше, чем они хотят. ..)

Но, конечно, если вы не освободите обработчик событий, объект, к которому он прикреплен, не будет освобожден до тех пор, пока память вашего процесса не будет освобождена сборщиком мусора при завершении работы. Но такое поведение вполне ожидаемо, вопреки тому, что вы, кажется, подразумеваете. Если вы ожидаете, что объект будет восстановлен, вам необходимо удалить все, что может поддерживать существование ссылки, включая обработчики событий.

person TheSmurf    schedule 18.08.2008
comment
Утечка имеет особое значение в контексте управляемого языка по сравнению с неуправляемым. Общепринятая номенклатура относится к объекту, оставленному в памяти дольше, чем его ожидаемое время жизни, как к утечке в управляемом коде. Вы правы, утверждая, что в неуправляемом мире, когда приложения не работают в защищенном режиме, память, не освобожденная обратно в ОС, не может быть восстановлена, но это не имеет ничего общего с вопросом ОП и не способствует обсуждению. - person Anderson Imes; 23.10.2009

Правда правда,

Вы правы, конечно. Но в этом мире рождается целое новое поколение программистов, которые никогда не коснутся неуправляемого кода, и я верю, что языковые определения будут изобретать себя заново снова и снова. Таким образом, утечки памяти в WPF отличаются от, скажем, C/Cpp.

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

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

person Arcturus    schedule 18.08.2008

Что ж, это (укус менеджера) я, конечно, понимаю и сочувствую.

Но как бы Microsoft это ни называла, я не думаю, что «новое» определение подходит. Это сложно, потому что мы не живем в управляемом на 100% мире (хотя Microsoft любит притворяться, что это так, сама Microsoft не живет в таком мире). Когда вы говорите об утечке памяти, вы можете иметь в виду, что программа потребляет слишком много памяти (это определение пользователя), или что управляемая ссылка не будет освобождена до выхода (как здесь), или что неуправляемая ссылка не очищается должным образом. up (это будет настоящая утечка памяти), или неуправляемый код, вызванный из управляемого кода, дает утечку памяти (еще одна настоящая утечка).

В этом случае очевидно, что означает «утечка памяти», хотя мы и неточно. Но становится ужасно утомительно разговаривать с некоторыми людьми, которые называют каждое чрезмерное потребление или неспособность собрать утечкой памяти; и обидно, когда эти люди программисты, которым якобы виднее. Думаю, для технических терминов важно иметь однозначное значение. Отладка становится намного проще, когда они это делают.

Так или иначе. Не хочу превращать это в легкомысленную дискуссию о языке. Просто говорю...

person TheSmurf    schedule 18.08.2008