I'm using the .NET4 WPF DataGrid to show a SQL table containing lots of images.
The XAML code in question is:
...
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Converter={StaticResource ImageConverter}, Path=Picture}" Stretch="Uniform" MaxHeight="200" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
...
And the ImageConverter is written as such:
[ValueConversion(typeof(Binary), typeof(BitmapImage))]
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = new System.IO.MemoryStream((value as Binary).ToArray());
bi.EndInit();
if (bi.CanFreeze) bi.Freeze();
return bi;
}
else return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My question is does the code above leak memory?
I have tried to do some profiling about this, but I'm not sure if I'm interpreting the results correctly.
First of all, the SQL table containing the images uses 38MB of disk space (the images should be stored in png format). After loading all the images to the datagrid via LINQ, the app uses some 170+ MB extra RAM. This can probably be contributed to uncompressing the images and the fact that the wpf datagrid is a gigantic memory hog even when virtualizing is enabled. After closing the window the memory usage does not drop. Reopening the window causes another 170+ MB of RAM to be used, making the total memory usage roughly 400MB. If I reopen the window again, then the memory usage drops to about 330MB. Reopening the window again takes the memory usage to 380MB. Reopening again takes it to 270MB. Reopening again takes it to 426MB. So as you can see very fluctuating...
This little bit of testing was done with Win7 x64 and 8GB of RAM (the app is compiled with the Any CPU option).
I did try the same test on a virtual xp machine with <512MB of ram (about 384 if I remember correctly). And everytime I reopened the window I first saw a dramatic drop in memory usage when the window was being loaded and then the same amout of memory reused, so the overall usage was about the same.
I interpret those results such that the GC doesn't bother cleaning up unless there is high pressure on the memory subsystem. But in that case calling GC.Collect() after I close the window should release the majority of the memory? Only that I didn't, I saw only a drop of a 4-6MB in memory usage when I called GC.Collect (I also tried all the possible parameters, including forcing collection on all generations and calling GC.WaitForPendingFinalizers()).
This got me thinking that maybe the unused leaking data is being pushed into the page file. But the page file usage drops and rises acording to the memory usage equaly.
All things considered there ought not to be a memory leak here, but I can't get the memory usage to come down after I have closed the window. I did try to attach some .NET profiler to my process, but thoses profilers are so complex that I can't quite figure out whether the shown image objects are still alive and referenced by something. Or are they dead and the GC simply hasn't cleaned them up...