Silverlight + MVVM + привязки = утечки памяти?

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

Очевидно, что это громадное и нелепое утверждение - поэтому я ожидаю, что у кого-то будет очевидный ответ о том, почему и где я ошибаюсь :)

Шаги по воспроизведению просты:

  • Свяжите свою модель представления с представлением, установив текст данных представлений на виртуальную машину (предположим, что модель представления использует INotifyPropertyChanged для поддержки привязки данных)
  • Привяжите элемент пользовательского интерфейса к свойству в модели представления, например:

<TextBox Text="{Binding SomeText}" />

  • Используйте привязку каким-либо образом (например, просто введите текстовое поле).

Это создает цепочку ссылок, которая простирается от корня до BindingExpression и вашей модели представления. Затем вы можете удалить представление из своего дерева пользовательского интерфейса, а также все ссылки на виртуальную машину - однако виртуальная машина никогда не будет собираться мусором благодаря корневой цепочке ссылок ‹> BindingExpression‹> ВМ.

Я создал два примера, иллюстрирующих проблему. У них есть кнопка для создания новой модели представления (которая должна сбрасывать все ссылки на старые модели) и кнопка, которая запускает сборку мусора и сообщает о текущем использовании памяти.

Пример 1 - это очень урезанный микро-пример калибратора. Пример 2 не использует никаких фреймворков и просто иллюстрирует проблему самым простым способом, который я мог придумать.

Пример 1

Пример 2

Для тех, кто хотел бы помочь, но не желает загружать примеры проектов, вот код, например 2. Мы начнем с модели просмотра под названием FooViewModel:

 public class FooViewModel : INotifyPropertyChanged
{
    string _fooText;

    public string FooText
    {
        get { return _fooText; }
        set
        {
            _fooText = value;
            NotifyPropertyChanged("FooText");
        }
    }

    private byte[] _data;
    public FooViewModel()
    {
        _data = new byte[10485760]; //use up 10mb of memory
    }



    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

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

Затем у нас есть представление под названием FooView, которое представляет собой пользовательский элемент управления, содержащий:

<UserControl x:Class="MVVMLeak.FooView">
    <StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
        <TextBlock Text="Bound textbox: " />
        <TextBox Text="{Binding FooText}" Width="100"/>
    </StackPanel>
</UserControl>

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

Важным моментом здесь является текстовое поле, привязанное к свойству FooText. Конечно, нам нужно установить текст данных, что я решил сделать в выделенном коде, а не вводить ViewModelLocator:

public partial class FooView : UserControl
{
    public FooView()
    {
        InitializeComponent();
        this.DataContext = new FooViewModel();
    }
}

MainPage выглядит так:

<StackPanel x:Name="LayoutRoot" Background="White">

    <Button Click="Button_Click" Content="Click for new FooView"/>
    <Button Click="Button2_Click" Content="Click to garbage collect"/>
    <ContentControl x:Name="myContent"></ContentControl>
</StackPanel>

со следующим в коде позади:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        myContent.Content = new FooView();
    }

    private void Button2_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Memory in use after collection: " + (GC.GetTotalMemory(true) / 1024 / 1024).ToString() + "MB");
    }

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

Стоит отметить, что эта статья базы знаний может иметь отношение к теме, однако я не уверен поскольку обходной путь «метод 2», похоже, не действует, и цепочка ссылок не соответствует.

Кроме того, я не уверен, что это имеет значение, но я использовал CLR Profiler, чтобы диагностировать причину.

Обновление:

Если кто-то захочет протестировать и сообщить о своих выводах в комментарии, я размещаю приложение silverlight через dropbox здесь: Пример размещения. Чтобы воспроизвести: нажмите верхнюю кнопку, введите что-нибудь, нажмите верхнюю кнопку, введите что-нибудь, нажмите верхнюю кнопку. Затем нажмите кнопку. Если он сообщает об использовании 10 МБ (или, возможно, какой-то другой объем, который не увеличивается), вы не испытываете утечки памяти.

На данный момент проблема, похоже, возникает на ВСЕХ наших машинах для разработки, которые представляют собой ThinkPad w510 (43192RU) с 12 ГБ оперативной памяти, 64-разрядную версию Win 7 Enterprise. Сюда входят некоторые, для которых не были установлены инструменты разработки. Возможно, стоит отметить, что они используют рабочую станцию ​​VMWare.

Проблема НЕ возникает на других машинах, которые я пробовал, включая несколько домашних ПК и другие ПК в офисе. Мы несколько исключили версии SL, объем памяти и, возможно, vmware. Все еще не определил причину.


person Shaun Rowan    schedule 17.05.2012    source источник
comment
Боковое примечание: один действительно уродливый / дрянный обходной путь для caliburn micro (и моего текущего подхода) - это установить для всех частных переменных значение null в переопределении OnDeactivate (true). По крайней мере, тогда мы утекаем медленнее, но в лучшем случае это бандаж.   -  person Shaun Rowan    schedule 18.05.2012
comment
Если утечка существует только во время выполнения, вам следует взглянуть на это: stackoverflow.com/questions/8544458/   -  person Guillaume Cantin    schedule 18.05.2012
comment
Я не смотрел этот образец, но предполагаю, что это та же проблема.   -  person Shaun Rowan    schedule 18.05.2012
comment
Я запустил пример 1, и утечки не было. Память оставалась на уровне 10 МБ независимо от того, сколько раз я нажимал новый FooView.   -  person Chui Tey    schedule 18.05.2012
comment
Вы что-то вводили в текстовое поле между каждым новым щелчком FooView?   -  person Shaun Rowan    schedule 18.05.2012
comment
Я пробовал ваш пример 1, и он не работает так, как вы ожидаете.   -  person Ondra    schedule 18.05.2012
comment
Bindins в silverlight по умолчанию - OneWay, поэтому, что бы вы ни делали, ваш FooViewModel.FooText никогда не будет обновлен.   -  person Ondra    schedule 18.05.2012
comment
Если я внес небольшие изменения, использованная память была 10742324, через пару щелчков мыши она изменилась на 10746260 (возможно, загрузка библиотеки ???), а затем после почти 30 щелчков и изменений текста значение не изменилось.   -  person Ondra    schedule 18.05.2012
comment
Могу я спросить, какую версию / платформу Silverlight вы используете? Для меня, если я [нажимаю кнопку 1, набираю что-нибудь, нажимаю кнопку 2], мне каждый раз сообщается о 10 дополнительных МБ.   -  person Shaun Rowan    schedule 18.05.2012
comment
Фактически, если я изменю код для выделения 100 МБ памяти, он выдаст исключение нехватки памяти через 10 раз (похоже, для SL существует ограничение в 1 ГБ)   -  person Shaun Rowan    schedule 18.05.2012
comment
Моя среда - W7, IE9 и SL5. Я тестировал его в бета-версиях VS2010 и VS11, нацеленных как на SL4, так и на SL5. Память немного подрастает (300 байт) вначале, но потом постоянно   -  person Ondra    schedule 18.05.2012
comment
Похоже, это может быть проблема с окружающей средой - я нашел коллегу, у которого тоже проблема не возникла. Я исследую различия и сообщу об этом. Спасибо!   -  person Shaun Rowan    schedule 18.05.2012
comment
Теперь у меня есть приложение silverlight, размещенное в Dropbox: dl.dropbox.com/u/ 72490893 / MVVMLeak / MVVMLeakTestPage.html. Я вижу поведение memleak в моей ОС хоста, но не в моей виртуальной машине. Не уверен, что здесь проверять, поскольку я убедился, что это одни и те же версии SL, работающие в одних и тех же браузерах. Оба работают в среде выполнения конечного пользователя.   -  person Shaun Rowan    schedule 18.05.2012
comment
Думали ли вы о повторной установке фреймворка? Вы проверяли наличие обновлений, которые могли бы решить вашу проблему? Я бы рассмотрел этот маршрут, поскольку это изолированное событие.   -  person Alex    schedule 22.05.2012
comment
Все клиенты SL обновлены. Все версии 32-битной / 64-битной среды выполнения / разработчика / конечного пользователя были опробованы ... Кажется, проблема не решена.   -  person Shaun Rowan    schedule 23.06.2012


Ответы (2)


Решение еще не найдено, однако проблема теперь обнаружена. Такое поведение наблюдается, если функции автоматизации Silverlights вызываются из-за:

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

Дополнительная информация здесь: http://www.wintellect.com/cs/blogs/sloscialo/archive/2011/04/13/silverlight-memory-leaks-and-automationpeers.aspx

Итак, возникает новая проблема: как отключить автоматизаторов или иным способом заставить их правильно очищаться?

Этот пост иллюстрирует один подход: утечка памяти WPF UserControl

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

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

Изменить:

Вот небольшой обходной путь, который, кажется, делает свою работу. Просто добавьте следующий параметр в свой HTML-код, в котором вы определяете объект silverlight:

<param name="windowless" value="true" />

Побочный эффект работы в безоконном режиме - автоматизация не работает :)

person Shaun Rowan    schedule 23.06.2012
comment
Насколько я понимаю, такого обходного пути для приложений вне браузера нет? - person John; 11.12.2013
comment
И, кстати, я не могу отблагодарить вас за то, что вы исследовали это. Видимо никого не волнует. - person John; 11.12.2013

Во втором примере утечки памяти нет.

После того, как вы измените новый FooView экземпляр на свой ContentControl с помощью myContent.Content = new FooView();, больше не используется ссылка на весь граф объекта View + ViewModel.

В конце концов, при необходимости, он будет собираться мусором.

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

person ken2k    schedule 27.05.2012
comment
Как упоминалось в обновлении, утечка, похоже, связана с ПК и / или средой, специфичной для определенных машин (возможно, небольшой части машин). Я согласен с тем, что не должно быть утечки памяти, но она определенно есть. Поскольку проблема до сих пор проявляется только на нашем ПК Thinkpad w510, я отложил проблему, так как у меня закончились идеи. - person Shaun Rowan; 29.05.2012