问题与BitmapImage相同,但为了演示,我们使用BitmapSource。基本上,如果我在视图中显示一个相当大的图像,并且在我完成它时将绑定对象设置为null,则无论您等待多长时间,图像内存都不会被回收。重现问题的完整代码:
XAML:
<Window x:Class="TestBitmapSource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestBitmapSource"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<Button Command="{Binding Path=ReloadCommand}">Load</Button>
<Button Command="{Binding Path=ReleaseCommand}">Release</Button>
<Image Source="{Binding Path=MyImage}"></Image>
</StackPanel>
</Grid>
C#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace TestBitmapSource
{
class MainViewModel : INotifyPropertyChanged
{
private BitmapSource myImage;
public BitmapSource MyImage
{
get { return myImage; }
set { myImage = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ICommand _reloadCommand;
public ICommand ReloadCommand
{
get
{
return _reloadCommand ?? (_reloadCommand = new CommandHandler(() => Reload(), true));
}
}
private ICommand _releaseCommand;
public ICommand ReleaseCommand
{
get
{
return _releaseCommand ?? (_releaseCommand = new CommandHandler(() => Release(), true));
}
}
Random random = new Random();
public void Reload()
{
int width = 6000;
int height = 6000;
int b = random.Next();
int g = random.Next();
int r = random.Next();
unsafe
{
byte* pixels = (byte*)Marshal.AllocHGlobal(width * height * 3);
for (int i = 0; i < width * height; i++)
{
pixels[i * 3] = (byte)b;
pixels[i * 3 + 1] = (byte)g;
pixels[i * 3 + 2] = (byte)r;
}
MyImage = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, (IntPtr)pixels, width * height * 3, width * 3);
Marshal.FreeHGlobal((IntPtr)pixels);
MyImage.Freeze();
}
}
public void Release()
{
MyImage = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
public MainViewModel()
{
MyImage = null;
}
}
public class CommandHandler : ICommand
{
private Action _action; private bool _canExecute;
public CommandHandler(Action action, bool canExecute) { _action = action; _canExecute = canExecute; }
public bool CanExecute(object parameter) { return _canExecute; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { _action(); }
}
}
要重现此问题,请单击“加载”按钮(将使用BitmapSource.Create创建图像),然后单击“释放”按钮,该按钮将尝试释放图像并收集未使用的内存。但是,进程监视器显示250MB的内存仍在使用中,如果我在Visual Studio中使用对象ID标记它,我知道MyImage对象仍然存活。
到目前为止,我发现了两件事:
1)如果我没有绑定到视图中的图像,一切都很好,图像对象立即消失。
2)如果再次单击“释放”按钮,标记的对象会突然消失并回收内存。
很明显,绑定行为会在视图模型中将图像对象设置为null时在某处创建一些未释放的引用。我想在进入内存分析器之前检查是否遗漏了一些明显的东西。
由于