До сих пор мое тестирование показало, что все стандартные подходы, примеры и фреймворки, использующие шаблон MVVM в silverlight, страдают от огромной проблемы: массивных утечек памяти, которые не позволяют виртуальным машинам собирать мусор.
Очевидно, что это громадное и нелепое утверждение - поэтому я ожидаю, что у кого-то будет очевидный ответ о том, почему и где я ошибаюсь :)
Шаги по воспроизведению просты:
- Свяжите свою модель представления с представлением, установив текст данных представлений на виртуальную машину (предположим, что модель представления использует INotifyPropertyChanged для поддержки привязки данных)
- Привяжите элемент пользовательского интерфейса к свойству в модели представления, например:
<TextBox Text="{Binding SomeText}" />
- Используйте привязку каким-либо образом (например, просто введите текстовое поле).
Это создает цепочку ссылок, которая простирается от корня до BindingExpression и вашей модели представления. Затем вы можете удалить представление из своего дерева пользовательского интерфейса, а также все ссылки на виртуальную машину - однако виртуальная машина никогда не будет собираться мусором благодаря корневой цепочке ссылок ‹> BindingExpression‹> ВМ.
Я создал два примера, иллюстрирующих проблему. У них есть кнопка для создания новой модели представления (которая должна сбрасывать все ссылки на старые модели) и кнопка, которая запускает сборку мусора и сообщает о текущем использовании памяти.
Пример 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. Все еще не определил причину.