2014-01-12 137 views
3

我有WPF窗口与网格控制imageGrid和按钮buttonRefresh。该代码仅用于测试目的,可能看起来有点奇怪。窗口代码:网格清理代码

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> 

回答

1

这是因为你的情况WriteableBitmap了内存泄露,这是在WPF一个老问题。在我的机器,程序占用的内存技嘉,我试图在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)

WPF BitmapImage Memory Leak

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,然后在gen1gen2,还有,因为他认为它是“活”的对象。从一代到gen2清洗很少,通常在几种情况:

  • 该系统具有低的物理内存。

  • 托管堆上分配的对象所使用的内存超过了可接受的阈值。随着流程运行,此阈值将不断调整。如果出现错误,可以通过垃圾来增加阈值。

  • GC.Collect方法被调用。在几乎所有情况下,您都不必调用此方法,因为垃圾收集器连续运行。此方法主要用于独特的情况和测试。

+0

@Alex Farber:请参阅我的关于问题的编辑 - “为什么GC.Collect()不起作用?”。 –