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

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

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

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

18.08.2008 00:49:33
11 ОТВЕТОВ
РЕШЕНИЕ

Я не согласен DannySmurf

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

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

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

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

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

Они советуют вам посмотреть на шаблон Weak Event

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

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

5
1.07.2016 11:58:59
Я отказываюсь, потому что ответ не имеет ничего общего с заданным вопросом. В общем, все знают о причинах утечки памяти и ее причинах, особенно из-за обработчиков событий, как вы упомянули. Здесь @Matt хотел знать, как безопасно обрабатывать обработчики событий при использовании внутри прикрепленного поведения. Я скоро опубликую ответ на это.
Saraf Talukder 4.06.2015 09:54:11

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

0
18.08.2008 00:58:04

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

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

3
18.08.2008 01:51:27

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

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

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

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

0
18.08.2008 03:40:18

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

  1. Шаблон слабых событий в WPF
  2. Шаблоны слабых событий (MSDN)
1
18.08.2008 08:48:25

@Arcturus:

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

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

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

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

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

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

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

0
18.08.2008 14:27:48
Утечка имеет специфическое значение в контексте управляемого языка по сравнению с неуправляемым. Обычно понимаемая номенклатура относится к объекту, оставленному в памяти дольше, чем его ожидаемое время жизни, как утечка в управляемом коде. Вы правы, утверждая, что в неуправляемом мире, когда приложения не работают в защищенном режиме, память, не освобожденная обратно в ОС, не может быть возвращена, но это не имеет ничего общего с вопросом ОП и не способствует обсуждению.
Anderson Imes 22.10.2009 21:40:44

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

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

Зачем реализовывать шаблон WeakEvent?

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

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

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

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

4
1.07.2016 11:59:58

Хорошо, что (менеджер немного) я, конечно, могу понять и сочувствовать.

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

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

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

-2
18.08.2008 15:27:19

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

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

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

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

0
18.08.2008 15:20:39

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

ue.PreviewKeyDown += ue_PreviewKeyDown;

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

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

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

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

5
29.05.2011 20:07:31
Это распространенное недоразумение. ue.PreviewKeyDown + = ue_PreviewKeyDown сохраняет строгую ссылку на ue, и поскольку us_PreviewKeyDown является статическим и пользователь никогда не будет собираться.
Daniel Bişar 22.03.2012 13:15:30
@ САКО, ты можешь это объяснить? Где хранится «сильная ссылка на тебя»? Насколько я понимаю, Джон абсолютно прав, в исходном примере нет абсолютно никакой утечки памяти. ue.PreviewKeyDown -= ue_PreviewKeyDownне обязательно.
Golvellius 30.01.2015 14:22:21
@Golvellius Я отправил ответ, который должен объяснить мою точку зрения. Я действительно проверил это сейчас и обнаружил, что утечки не будет, если ue_PreviewKeyDown статичен.
Daniel Bişar 30.01.2015 15:35:37

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

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, чтобы обнулить обе ссылки. Если вы установили только a в значение null, b останется в живых, а не a (что доказывает, что Джон Фентон ошибочно заявляет, что в поставщике событий хранится жесткая ссылка - в данном случае a).

Это привело меня к неверному выводу, что

a.Clicked += B.StaticHandleClicked;

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

A.StaticClicked += b.HandleClicked;

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

2
30.01.2015 15:33:53
Спасибо, что потратили время на ответ на мой комментарий, НО: Джон Фентон не ошибся, заявив, что в поставщике событий хранится жесткая ссылка. Устанавливая значение anull, вы в основном удаляете «жесткую» ссылку, поскольку объект памяти, на который aуказывает объект, впоследствии будет собираться мусором. И a.Clicked += B.StaticHandleClicked;это именно та ситуация ОП - это никогда не может вызвать утечку памяти, поэтому вопрос Джона Фентона Итак ... Где утечка? , Как вы указали, только наоборот, но это не та ситуация, в которой находится ОП.
Golvellius 30.01.2015 16:07:01