Я создал «прикрепленное поведение» в своем приложении WPF, которое позволяет мне обрабатывать нажатие клавиши Enter и переходить к следующему элементу управления. Я называю это EnterKeyTraversal.IsEnabled, и вы можете увидеть код в моем блоге здесь .
Моя главная проблема сейчас заключается в том, что у меня может быть утечка памяти, так как я обрабатываю событие PreviewKeyDown в UIElements и никогда явно не «отцепляю» событие.
Как лучше всего предотвратить эту утечку (если она действительно есть)? Должен ли я сохранить список элементов, которыми я управляю, и отцепить событие PreviewKeyDown в событии Application.Exit? Кто-нибудь имел успех с присоединенным поведением в своих собственных приложениях WPF и придумал элегантное решение для управления памятью?
Я не согласен DannySmurf
Некоторые объекты макета WPF могут засорить вашу память и сделать ваше приложение действительно медленным, когда они не собирают мусор. Поэтому я нахожу правильный выбор слов: вы теряете память на объекты, которые больше не используете. Вы ожидаете, что элементы будут собраны сборщиком мусора, но это не так, потому что где-то есть ссылка (в данном случае в обработчике события from).
Теперь за реальный ответ :)
Я советую вам прочитать эту статью о производительности WPF на MSDN
Невозможность удаления обработчиков событий на объектах может сохранить объекты живыми
Делегат, который объект передает своему событию, фактически является ссылкой на этот объект. Таким образом, обработчики событий могут поддерживать объекты дольше, чем ожидалось. При выполнении очистки объекта, который зарегистрирован для прослушивания события объекта, важно удалить этот делегат перед освобождением объекта. Сохранение ненужных объектов увеличивает использование памяти приложением. Это особенно верно, когда объект является корнем логического дерева или визуального дерева.
Они советуют вам посмотреть на шаблон Weak Event
Другое решение - удалить обработчики событий, когда вы закончите с объектом. Но я знаю, что с присоединенными свойствами эта точка не всегда может быть ясна ..
Надеюсь это поможет!
Убедитесь, что элементы ссылки на события находятся в объекте, на который они ссылаются, как текстовые поля в элементе управления формы. Или, если это не может быть предотвращено. Создайте статическое событие в глобальном вспомогательном классе, а затем отслеживайте события в глобальном вспомогательном классе. Если эти два шага не могут быть выполнены, попробуйте использовать WeakReference, они обычно идеально подходят для этих ситуаций, но они идут с накладными расходами.
@Nick Да, вещь с прикрепленным поведением заключается в том, что по определению они не находятся в одном объекте с элементами, чьи события вы обрабатываете.
Я думаю, что ответ лежит как-то в использовании WeakReference, но я не видел простых примеров кода, которые бы мне его объяснили. :)
Я только что прочитал твой пост в блоге и думаю, что ты получил несколько вводящих в заблуждение советов, Мэтт. Если здесь есть реальная утечка памяти , то это ошибка в .NET Framework, которую нельзя обязательно исправить в коде.
Я думаю, что вы (и постер в своем блоге) на самом деле говорите здесь не о утечке, а о постоянном потреблении памяти. Это не одно и то же. Для ясности, утечка памяти - это память, которая зарезервирована программой, а затем заброшена (т. Е. Указатель оставлен висящим) и которая впоследствии не может быть освобождена. Поскольку память управляется в .NET, это теоретически невозможно. Однако для программы возможно зарезервировать постоянно увеличивающийся объем памяти, не позволяя ссылкам на нее выходить за рамки (и становиться пригодными для сбора мусора); однако эта память не просочилась. GC вернет его в систему после выхода из вашей программы.
Так. Чтобы ответить на ваш вопрос, я не думаю, что у вас действительно есть проблема здесь. У вас, конечно, нет утечки памяти, и из вашего кода я не думаю, что вам нужно беспокоиться о том, насколько расходуется память. До тех пор, пока вы убедитесь, что вы не назначаете этот обработчик событий повторно, не отменяя его (т. Е. Либо устанавливаете его только один раз, либо удаляете его ровно один раз при каждом назначении), что Вы, кажется, делаете, ваш код должен быть в порядке.
Похоже, что это совет, который постарался дать вам постер в вашем блоге, но он использовал эту тревожную работу «утечка», которая является пугающим словом, но о которой многие программисты забыли о реальном значении в управляемом мире; это не относится здесь.
Хотели ли вы реализовать «Шаблон слабых событий» вместо обычных событий?
@Arcturus:
... засорять память и замедлять работу приложения, когда они не собирают мусор.
Это очевидно, и я не согласен. Однако:
... вы теряете память на объект, который больше не используете ... потому что есть ссылка на них.
«память выделяется для программы, и эта программа впоследствии теряет возможность доступа к ней из-за недостатков логики программы» (Википедия, «Утечка памяти»)
Если есть активная ссылка на объект, к которому ваша программа может получить доступ, то по определению это не утечка памяти. Утечка означает, что объект больше недоступен (для вас или для OS / Framework) и не будет освобожден в течение всего времени текущего сеанса операционной системы . Это не тот случай, здесь.
(Извините, что я семантический нацист… может быть, я немного староват, но утечка имеет очень специфическое значение. В наши дни люди склонны использовать «утечку памяти» для обозначения всего, что потребляет на 2 КБ памяти больше, чем они хотят. ..)
Но, конечно, если вы не освободите обработчик события, объект, к которому он присоединен, не будет освобожден, пока память вашего процесса не будет освобождена сборщиком мусора при завершении работы. Но это поведение вполне ожидаемо, вопреки тому, что вы, похоже, подразумеваете. Если вы ожидаете, что объект будет возвращен, вам нужно удалить все, что может сохранить ссылку, включая обработчики событий.
Да, я знаю, что в старые времена утечки памяти были совершенно другой темой. Но с управляемым кодом, новый смысл термина утечки памяти может быть более подходящим ...
Microsoft даже признает, что это утечка памяти:
Зачем реализовывать шаблон WeakEvent?
Прослушивание событий может привести к утечкам памяти. Типичная техника для прослушивания события заключается в использовании специфичного для языка синтаксиса, который присоединяет обработчик к событию в источнике. Например, в C # этот синтаксис: source.SomeEvent + = new SomeEventHandler (MyEventHandler).
Этот метод создает сильную ссылку из источника события на слушателя события. Обычно присоединение обработчика события для слушателя приводит к тому, что у слушателя будет время жизни объекта, на которое влияет время жизни объекта для источника (если обработчик события явно не удален). Но в определенных обстоятельствах вы можете захотеть, чтобы время жизни объекта слушателя контролировалось только другими факторами, такими как то, принадлежит ли он в настоящее время к визуальному дереву приложения, а не временем жизни источника. Всякий раз, когда время жизни исходного объекта выходит за пределы срока жизни объекта слушателя, нормальный шаблон событий приводит к утечке памяти: слушатель остается живым дольше, чем предполагалось.
Мы используем WPF для клиентского приложения с большими инструментальными окнами, которые можно перетаскивать, все изящные вещи и все они совместимы с XBAP. Но у нас была та же проблема с некоторыми инструментальными окнами, которые не были собраны мусором. к тому факту, что он все еще зависел от прослушивателей событий .. Теперь это может не быть проблемой, когда вы закрываете окно и закрываете свое приложение. Но если вы создаете очень большие ToolWindows с большим количеством команд, и все эти команды пересматриваются снова и снова, и люди должны использовать ваше приложение целый день ... Я могу вам сказать ... это действительно забивает вашу память и время отклика вашего приложения ..
Кроме того, мне гораздо легче объяснить моему менеджеру, что у нас есть утечка памяти, чем объяснить ему, что некоторые объекты не являются сборщиком мусора из-за некоторых событий, требующих очистки;)
Хорошо, что (менеджер немного) я, конечно, могу понять и сочувствовать.
Но как бы Microsoft это ни назвала, я не думаю, что «новое» определение уместно. Это сложно, потому что мы не живем в мире, управляемом на 100% (хотя Microsoft любит притворяться, что мы это делаем, сама Microsoft не живет в таком мире). Когда вы говорите об утечке памяти, вы можете иметь в виду, что программа потребляет слишком много памяти (это определение пользователя), или что управляемая ссылка не будет освобождена до выхода (как здесь), или что неуправляемая ссылка не очищается должным образом up (это может быть реальной утечкой памяти) или неуправляемый код, вызываемый из управляемого кода, вызывает утечку памяти (еще одна настоящая утечка).
В этом случае очевидно, что означает «утечка памяти», хотя мы и неточны. Но становится ужасно утомительно разговаривать с некоторыми людьми, которые называют каждое чрезмерное потребление или сбой в обнаружении утечки памяти; и это расстраивает, когда эти люди - программисты, которые якобы знают лучше. Думаю, для технических терминов важно иметь однозначные значения. Отладка намного проще, когда они делают.
Так или иначе. Не хочу превращать это в воздушную сказку о языке. Просто говорю...
Правда правда,
Вы, конечно, правы. Но в этом мире рождается целое поколение программистов, которые никогда не коснутся неуправляемого кода, и я верю, что определения языка будут изобретаться снова и снова. Утечки памяти в WPF таким образом отличаются от C / Cpp.
Или, конечно, моим менеджерам я назвал это утечкой памяти ... моим коллегам я назвал это проблемой производительности!
Ссылаясь на проблему Мэтта, это может быть проблема производительности, которую вам, возможно, придется решить. Если вы просто используете несколько экранов и делаете эти элементы управления одиночными, вы можете вообще не увидеть эту проблему;).
Если не считать философских дебатов, то, просматривая сообщение в блоге ОП, я не вижу здесь никакой утечки:
ue.PreviewKeyDown += ue_PreviewKeyDown;
Жесткая ссылка на ue_PreviewKeyDown
хранится в ue.PreviewKeyDown
.
ue_PreviewKeyDown
это STATIC
метод и не может быть GCed
.
Не ue
хранится никаких жестких ссылок на него, поэтому ничто не мешает ему быть GCed
.
Итак ... Где утечка?
ue.PreviewKeyDown -= ue_PreviewKeyDown
не обязательно. Чтобы объяснить мой комментарий к посту Джона Фентона, вот мой ответ. Давайте посмотрим на следующий пример:
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;
ссылка будет сохранена на б.
a
null, вы в основном удаляете «жесткую» ссылку, поскольку объект памяти, на который a
указывает объект, впоследствии будет собираться мусором. И a.Clicked += B.StaticHandleClicked;
это именно та ситуация ОП - это никогда не может вызвать утечку памяти, поэтому вопрос Джона Фентона Итак ... Где утечка? , Как вы указали, только наоборот, но это не та ситуация, в которой находится ОП.