Reputation: 43331
I have WPF window with Grid control imageGrid
and button buttonRefresh
. The code is for testing purposes and may look a bit strange. Window code:
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);
}
}
}
}
When this program starts, FillGrid
function runs successfully. When Refresh button is clicked, FillGrid
is executed again, and this time new WriteableBitmap
line throws OutOfMemoryException
. I think that ClearGrid
function doesn't release all resources, and bitmaps
array is not destroyed yet. What is the problem in this code?
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>
Upvotes: 3
Views: 157
Reputation: 22702
This is because in your case WriteableBitmap
creates a memory leak, it's an old problem in WPF. On my machine, the program takes a gigabyte of memory, I tried to install in RenderMode
to SoftwareOnly
:
using System.Windows.Interop;
public RenderMode RenderMode { get; set; }
RenderMode = RenderMode.SoftwareOnly;
as advised here, but it did not help. Also tried forced to call GarbageCollector
:
GC.Collect();
in your ClearGrid()
method, but it did not help.
You need to try to see the solution, which is published here:
Silverlight's Big Image Problem (and What You Can Do About It)
Why GC.Collect() doesn't help?
This topic is pretty extensive, but I'll try to briefly describe the reason. In most cases, developers should not call the garbage collector manually, because the collector pretty intelligent and runs continuously, and if he could clean up the object from heap memory, he would have done it. Only in very rare and exclusive situations should call it manually, and it should occur after several performance tests. I also want to quote from this answer (Best Practice for Forcing Garbage Collection in C#):
The .NET GC is well designed and tuned to be adaptive, which means it can adjust GC0/1/2 threshold according to the "habit" of your program memory usage. So, it will be adapted to your program after some time running. Once you invoke GC.Collect explicitly, the thresholds will be reset! And the .NET has to spent time to adapt to your program's "habit" again.
WriteableBitmap
has bugs that can not be fixed, that's an example of one:
WPF RenderTargetBitmap still leaking, and badly
Garbage collector encountering object puts it gradually (first in gen0
, then in gen1
) in the second generation gen2
, and there remains, because he believes it "live" object. From generation to gen2
is cleaned very rarely, usually on several cases:
The system has low physical memory.
The memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs. In the case of a bug, threshold can be increased yourself by garbage.
The GC.Collect method is called. In almost all cases, you do not have to call this method, because the garbage collector runs continuously. This method is primarily used for unique situations and testing.
Upvotes: 1