我有带网格控件imageGrid
和按钮buttonRefresh
的WPF窗口。该代码用于测试目的,可能看起来有点奇怪。窗口代码:
public partial class MainWindow : Window
{
const int gridWidth = 10;
const int gridHeight = 20;
const int cellWidth = 100;
const int cellHeight = 100;
const int bitmapWidth = 1024;
const int bitmapHeight = 1024;
WriteableBitmap[,] bitmaps;
public MainWindow()
{
InitializeComponent();
buttonRefresh.Click += new RoutedEventHandler(buttonRefresh_Click);
FillGrid();
}
void buttonRefresh_Click(object sender, RoutedEventArgs e)
{
FillGrid();
}
void FillGrid()
{
ClearGrid();
CreateBitmaps();
InitGrid();
}
void ClearGrid()
{
imageGrid.Children.Clear();
imageGrid.RowDefinitions.Clear();
imageGrid.ColumnDefinitions.Clear();
bitmaps = null;
}
void InitGrid()
{
for (int i = 0; i < gridWidth; ++i)
{
ColumnDefinition coldef = new ColumnDefinition();
coldef.Width = GridLength.Auto;
imageGrid.ColumnDefinitions.Add(coldef);
}
for (int i = 0; i < gridHeight; ++i)
{
RowDefinition rowdef = new RowDefinition();
rowdef.Height = GridLength.Auto;
imageGrid.RowDefinitions.Add(rowdef);
}
for (int y = 0; y < gridHeight; ++y)
{
for (int x = 0; x < gridWidth; ++x)
{
Image image = new Image();
image.Width = cellWidth;
image.Height = cellHeight;
image.Margin = new System.Windows.Thickness(2);
image.Source = bitmaps[y, x];
imageGrid.Children.Add(image);
Grid.SetRow(image, y);
Grid.SetColumn(image, x);
}
}
}
void CreateBitmaps()
{
bitmaps = new WriteableBitmap[gridHeight, gridWidth];
byte[] pixels = new byte[bitmapWidth * bitmapHeight];
Int32Rect rect = new Int32Rect(0, 0, bitmapWidth, bitmapHeight);
for (int y = 0; y < gridHeight; ++y)
{
for (int x = 0; x < gridWidth; ++x)
{
bitmaps[y, x] = new WriteableBitmap(bitmapWidth, bitmapHeight, 96, 96, PixelFormats.Gray8, null);
byte b = (byte)((10 * (x + 1) * (y + 1)) % 256);
for (int n = 0; n < bitmapWidth * bitmapHeight; ++n)
{
pixels[n] = b;
}
bitmaps[y, x].WritePixels(rect, pixels, bitmapWidth, 0);
}
}
}
}
此程序启动时,FillGrid
功能成功运行。单击“刷新”按钮后,将再次执行FillGrid
,此次new WriteableBitmap
行将引发OutOfMemoryException
。我认为ClearGrid
函数不释放所有资源,bitmaps
数组尚未销毁。这段代码有什么问题?
XAML:
<Window x:Class="Client.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Grid and DirectX" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button HorizontalAlignment="Center" Padding="20 2" Margin="0 2" Name="buttonRefresh">
Refresh
</Button>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
<Grid Name="imageGrid"/>
</ScrollViewer>
</Grid>
</Window>
答案 0 :(得分:1)
这是因为在您的情况下WriteableBitmap
会造成内存泄漏,这是WPF中的一个老问题。在我的机器上,程序占用了1 GB的内存,我尝试在RenderMode
安装到SoftwareOnly
:
using System.Windows.Interop;
public RenderMode RenderMode { get; set; }
RenderMode = RenderMode.SoftwareOnly;
建议here,但没有帮助。还尝试强制拨打GarbageCollector
:
GC.Collect();
在您的ClearGrid()
方法中,但没有帮助。
您需要尝试查看此处发布的解决方案:
Silverlight's Big Image Problem (and What You Can Do About It)
<强> Why GC.Collect() doesn't help?
强>
这个主题非常广泛,但我会尝试简要描述一下原因。在大多数情况下,开发人员不应该手动调用垃圾收集器,因为收集器非常智能并且连续运行,如果他可以从堆内存中清除对象,他就会这样做。只有在非常罕见和独占的情况下才应手动调用它,并且应该在几次性能测试之后进行。我也想引用这个答案(Best Practice for Forcing Garbage Collection in C#):
.NET GC经过精心设计和调整为自适应,这意味着它可以根据程序内存使用的“习惯”调整GC0 / 1/2阈值。因此,它会在运行一段时间后适应您的程序。一旦显式调用GC.Collect,阈值将被重置! .NET必须花时间再次适应你的程序的“习惯”。
WriteableBitmap
有无法解决的错误,这是一个例子:
WPF RenderTargetBitmap still leaking, and badly
遇到对象的垃圾收集器在第二代gen0
中逐渐将其放入(gen1
中的第一个,然后放入gen2
),并且仍然存在,因为他认为它是“活的”对象。从一代到gen2
很少被清理,通常是在几种情况下:
系统物理内存较低。
托管堆上已分配对象使用的内存超过了可接受的阈值。在该过程运行时不断调整该阈值。在出现错误的情况下,可以通过垃圾自行增加阈值。
调用GC.Collect方法。几乎在所有情况下,您都不必调用此方法,因为垃圾收集器会持续运行。此方法主要用于特殊情况和测试。