2014-01-12 136 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()不起作用?」。 –