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

StackOverflow https://stackoverflow.com/questions/14061

  •  08-06-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

Другие советы

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

ue.PreviewKeyDown += ue_PreviewKeyDown;

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

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

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

Итак...Где произошла утечка?

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

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

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

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

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

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

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

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

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

Думали ли вы о внедрении "Слабого шаблона событий" вместо обычных событий?

  1. Слабый шаблон событий в WPF
  2. Слабые шаблоны событий (MSDN)

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

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

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

a.Clicked += B.StaticHandleClicked;

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

A.StaticClicked += b.HandleClicked;

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

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

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

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

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

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

@Арктур:

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

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

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

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

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

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

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

Истинно истинный,

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top