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

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

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

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

Ответ 1

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

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

Теперь для реального ответа:)

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

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

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

Они советуют вам изучить Слабый шаблон событий

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Философские дебаты в сторону, глядя на сообщение блога OP, я не вижу утечки здесь:

ue.PreviewKeyDown += ue_PreviewKeyDown;

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

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

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

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

Ответ 4

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

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

Ответ 6

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

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

Это привело меня к НЕПРАВИЛЬНО заключению, что

a.Clicked += B.StaticHandleClicked;

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

A.StaticClicked += b.HandleClicked;

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

Ответ 7

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

Ответ 8

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

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

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

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

Ответ 9

@Arcturus:

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

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

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

"выделена программе, и эта программа впоследствии теряет возможность доступа к ней из-за ошибок в программной логике" (Wikipedia, "Memory leak" )

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

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

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

Ответ 10

Правда,

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

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

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

Ответ 11

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

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

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

В любом случае. Не нужно превращать это в воздушно-феевую дискуссию о языке. Просто говорю...