网格清理代码

3

我有一个WPF窗口,其中包含一个名为imageGrid的Grid控件和一个名为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个回答

1
这是因为在您的情况下,WriteableBitmap 会造成内存泄漏,在WPF中这是一个老问题。在我的机器上,程序占用了一GB的内存,我尝试将其安装在RenderModeSoftwareOnly
using System.Windows.Interop;

public RenderMode RenderMode { get; set; }

RenderMode = RenderMode.SoftwareOnly;

如建议此处,但并没有帮助。也尝试强制调用GarbageCollector

GC.Collect();

在你的ClearGrid()方法中添加了代码,但并没有起到帮助作用。

你需要尝试查看已发布的解决方案:

Silverlight的大图像问题(以及您可以做什么)

WPF BitmapImage内存泄漏

为什么GC.Collect()没有帮助?

这个话题相当广泛,但我会简要地描述原因。在大多数情况下,开发人员不应该手动调用垃圾收集器,因为垃圾收集器非常智能并且持续运行,如果它可以从堆内存中清除对象,它就会这样做。只有在非常罕见和独特的情况下才需要手动调用它,并且应在进行几次性能测试后进行。我还想引用这个答案(C#强制垃圾回收的最佳实践)中的一句话:

.NET的垃圾回收器经过精心设计和调整,具有自适应性,可以根据程序内存使用的“习惯”来调整GC0/1/2阈值。因此,在运行一段时间后,它将适应您的程序。一旦您显式调用GC.Collect,阈值将被重置!而且.NET必须花时间再次适应您的程序的“习惯”。 WriteableBitmap存在无法修复的错误,以下是一个示例: WPF RenderTargetBitmap still leaking, and badly 垃圾收集器遇到对象时,会逐渐将其放入第二代gen2中(首先在gen0中,然后在gen1中),并保留在那里,因为它认为它是“活”的对象。从一代到gen2很少清除,通常只有几种情况:
  • 系统物理内存较低。

  • 托管堆上已分配对象所使用的内存超过了可接受的阈值。该阈值在进程运行时不断调整。如果存在漏洞,可以通过垃圾回收来增加阈值。

  • 调用GC.Collect方法。在几乎所有情况下,您不需要调用此方法,因为垃圾收集器会持续运行。该方法主要用于特殊情况和测试。


@Alex Farber:请看一下我的关于问题“为什么GC.Collect()没有帮助”的编辑。 - Anatoliy Nikolaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接