垃圾回收器的替代方案是什么?

8

我有以下代码,运行时会生成一个 OutOfMemoryException 异常:

public partial class MainWindow : Window
{
    private DrawingVisual myVisual = new DrawingVisual();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        myVisual = GetVisual();
        graphicsCanvas.AddVisual(myVisual);
    }

    private void Graphics_Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        //get the data visual:
        DrawingVisual tempVisual = GetVisual();

        //first clear the current display data:
        graphicsCanvas.RemoveVisual(myVisual);

        //get the data visual:
        myVisual = tempVisual;

        graphicsCanvas.AddVisual(myVisual);

        //GC.Collect();
    }

    private DrawingVisual GetVisual()
    {
        double width = graphicsCanvas.ActualWidth;
        double height = graphicsCanvas.ActualHeight;

        DrawingVisual dV = new DrawingVisual();

        Rect clipRect = new Rect(0, 0, width, height);

        dV.Clip = new RectangleGeometry(clipRect);

        using (DrawingContext dC = dV.RenderOpen())
        {
            RenderTargetBitmap rTB = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);

            if (rTB.CanFreeze)
            {
                rTB.Freeze();
            }

            dC.DrawImage(rTB, clipRect);
        }

        return dV;
    }
}

如果定义了一个名为Graphics_Canvas的变量:

class Graphics_Canvas : Canvas
{
    private List<DrawingVisual> visuals = new List<DrawingVisual>();

    protected override int VisualChildrenCount
    {
        get { return visuals.Count; }
    }

    protected override Visual GetVisualChild(int index)
    {
        return visuals[index];
    }

    public void AddVisual(DrawingVisual visual)
    {
        visuals.Add(visual);

        base.AddVisualChild(visual);
        base.AddLogicalChild(visual);
    }

    public bool ContainsVisual(DrawingVisual visual)
    {
        return visuals.Contains(visual);
    }

    public bool HasVisuals
    {
        get { return visuals.Count > 0; }
    }

    public void RemoveAllVisuals()
    {
        for (int i = 0; i < visuals.Count; i++)
        {
            RemoveFromLogicalTree(visuals[i]);
        }

        visuals.Clear();
    }

    private void RemoveFromLogicalTree(Visual visual)
    {
        RemoveLogicalChild(visual);
        RemoveVisualChild(visual);
    }

    public void RemoveLastVisual()
    {
        if (visuals.Count > 0)
        {
            int index = visuals.Count - 1;

            RemoveFromLogicalTree(visuals[index]);
            visuals.Remove(visuals[index]);
        }     
    }

    public void RemoveVisual(DrawingVisual visual)
    {
        RemoveFromLogicalTree(visual);
        visuals.Remove(visual);            
    }
}

创建Window的XAML代码如下:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:csMemoryLeakTestProject" x:Class="csMemoryLeakTestProject.MainWindow"
    Title="MainWindow" 
    Background="Gray"
    Height="600" Width="800"
    WindowStartupLocation="CenterScreen"
    Loaded="Window_Loaded">
    <Grid>
        <local:Graphics_Canvas Margin="12" Background="White" x:Name="graphicsCanvas" MouseMove="Graphics_Canvas_MouseMove"/>
    </Grid>
</Window>

现在,这只是一个例子来说明我的观点,即我不理解在使用垃圾回收器之外还有什么替代方案... 如果运行程序并不断移动鼠标到Graphics_Canvas上,内存使用量会不断增加直到出现OutOfMemoryException。如果您在我已经注释掉的地方添加GC.Collect(),这种情况就不会发生(虽然由于某些原因,内存会略微增加,希望与我的问题有关),程序将继续运行。
那么,为什么GC没有启动并清除这段内存以防止异常发生?如果我犯了一些非常基本的错误,我很高兴有人指出来,这样我就可以超越这个问题。
我在这个网站和其他网站上看到过许多程序员建议“永远不要使用垃圾回收器”。我想遵守最佳实践,但在这种情况下,我不知道还能做些什么。
2个回答

10

程序不能管理大量内存并不是GC的错误。 你的程序中你这样做:

private void Graphics_Canvas_MouseMove(object sender, MouseEventArgs e)
{
   ....
    //ADD ELEMENTS ON EVERY MOVE !
    graphicsCanvas.AddVisual(myVisual);

    //GC.Collect();
}

鼠标移动时添加一个元素,这样就增加了集合的大小。你预期会发生什么?

因此,在90%的情况下,这类问题的解决方案是重新设计代码。

例如:

几乎不可能每次MouseMove都需要向visual的children集合添加新元素。也许可以重用已经存在的元素。

明确使用GC并不被建议,但有时我们需要使用它。但是,我再次强调,在这种情况下,我几乎无法相信你需要以那种方式管理程序。


2
说实话,我觉得很奇怪,系统在尝试GC.Collect()之前就抛出了OutOfMemoryException异常。这只是我的个人看法。 - Nolonar
4
等等,这段代码首先移除一个元素然后再添加另一个元素。没问题。 - Konrad Rudolph
1
@Tigran 你说得对,我不需要在MouseMove事件上添加Visual,但这只是一个快速生成错误的示例。另外,在添加新的Visual之前,我会先删除一个Visual,如果我在每个MouseMove事件中输出Canvas上的Visual数量,它将保持恒定为1。 - Greg
2
@Tigran,那不是我的重点。我的重点是这是一种可接受的使用模式,GC通常能够处理。你的回答强烈暗示代码只是将对象添加到集合中而没有删除它们,并且OP的代码会导致内存泄漏。但事实并非如此。 - Konrad Rudolph
1
@KonradRudolph:它在鼠标移动时添加了该代码,GC可能没有时间收集已删除的潜在适当对象,如果有这样的对象。我的观点是:使用该模式的位置是错误的。我的意思是在mousemove中使用Add/Remove。因为您无法预测GC的延迟,这可能会在正常程序运行期间发生,并导致内存泄漏。 - Tigran
显示剩余3条评论

-1

1
如果那是问题的话,调用 GC.Collect() 也无法解决它。 - RichieHindle
@IvoTops 是的,我已经了解了这个问题。然而,上面发布的代码是整个程序的全部代码,我没有添加任何事件,所以我不确定这是否可能是原因。如果Canvas在幕后自动添加事件,那么我还能做些什么来删除它们呢? - Greg

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