Предотвращение утечек памяти с прикрепленным поведением - PullRequest
11 голосов
/ 18 августа 2008

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

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

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

Ответы [ 11 ]

5 голосов
/ 18 августа 2008

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

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

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

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

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

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

Они советуют вам изучить паттерн Weak Event

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

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

4 голосов
/ 29 мая 2011

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

ue.PreviewKeyDown += ue_PreviewKeyDown;

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

ue_PreviewKeyDown является методом STATIC и не может быть GCed.

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

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

4 голосов
/ 18 августа 2008

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

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

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

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

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

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

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

2 голосов
/ 18 августа 2008

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

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

1 голос
/ 30 января 2015

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

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

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

a.Clicked += B.StaticHandleClicked;

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

A.StaticClicked += b.HandleClicked;

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

1 голос
/ 18 августа 2008

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

  1. Шаблон слабых событий в WPF
  2. Шаблоны слабых событий (MSDN)
0 голосов
/ 18 августа 2008

Верно верно,

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

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

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

0 голосов
/ 18 августа 2008

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

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

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

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

0 голосов
/ 18 августа 2008

@ Арктур:

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

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

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

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

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

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

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

0 голосов
/ 18 августа 2008

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

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

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

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

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