【问题标题】:Why is my c# paint method running out of memory?为什么我的 c#paint 方法内存不足?
【发布时间】:2011-04-09 13:49:18
【问题描述】:

我是 C# 新手,并尝试通过编写一些简单的应用程序来学习以熟悉语法和 .NET 库。我最近接的一个小项目是极地时钟like the one found here。

我早期注意到的一个问题是应用程序会不断“闪烁”,这确实与演示无关,因此我在网上阅读了有关如何实现双缓冲区的信息,它消除了这个问题,但可能会或可能会与问题无关。这是我的onPaint 方法;它由定时器控制每 33ms (~30 FPS) 调用一次。应用程序的大部分其余部分只是用于拖动应用程序(因为它是无框架且具有透明背景)、双击退出等的处理程序。

    protected override void OnPaint(PaintEventArgs e) {
        DateTime now = DateTime.Now;

        float secondAngle = now.Second / 60F;
        secondAngle += (now.Millisecond / 1000F) * (1F / 60F);

        float minuteAngle = now.Minute / 60F;
        minuteAngle += secondAngle / 60F;

        float hourAngle = now.Hour / 24F;
        hourAngle += minuteAngle / 60F;

        float dayOfYearAngle = now.DayOfYear / (365F + (now.Year % 4 == 0 ? 1F : 0F));
        dayOfYearAngle += hourAngle / 24F;

        float dayOfWeekAngle = (float)(now.DayOfWeek + 1) / 7F;
        dayOfWeekAngle += hourAngle / 24F;

        float dayOfMonthAngle = (float)now.Day / (float)DateTime.DaysInMonth(now.Year, now.Month);
        dayOfMonthAngle += hourAngle / 24F;

        float monthAngle = now.Month / 12F;
        monthAngle += dayOfMonthAngle / (float)DateTime.DaysInMonth(now.Year, now.Month);

        float currentPos = brushWidth / 2F;

        float[] angles = {
            secondAngle, minuteAngle,
            hourAngle, dayOfYearAngle,
            dayOfWeekAngle, dayOfMonthAngle,
            monthAngle
        };

        SolidBrush DateInfo = new SolidBrush(Color.Black);
        SolidBrush background = new SolidBrush(Color.Gray);
        Pen lineColor = new Pen(Color.Blue, brushWidth);
        Font DateFont = new Font("Arial", 12);

        if (_backBuffer == null) {
            _backBuffer = new Bitmap(this.Width, this.Height);
        }

        Graphics g = Graphics.FromImage(_backBuffer);
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        try {                
            g.Clear(Color.White);
            if (_mouseIsOver) {
                g.FillEllipse(background, new Rectangle(0, 0, this.Width, this.Height));
            }
            foreach (float angle in angles) {
                g.DrawArc(
                    lineColor,
                    currentPos, currentPos,
                    this.Height - currentPos * 2, this.Width - currentPos * 2,
                    startAngle, angle * 360F
                );

                currentPos += brushWidth + spaceStep;
            }

            // Text - Seconds

            g.DrawString(String.Format("{0:D2} s", now.Second), DateFont, DateInfo, new PointF(115F, 0F));
            g.DrawString(String.Format("{0:D2} m", now.Minute), DateFont, DateInfo, new PointF(115F, 20F));
            g.DrawString(String.Format("{0:D2} h", now.Hour), DateFont, DateInfo, new PointF(115F, 40F));
            g.DrawString(String.Format("{0:D3}", now.DayOfYear), DateFont, DateInfo, new PointF(115F, 60F));
            g.DrawString(now.ToString("ddd"), DateFont, DateInfo, new PointF(115F, 80F));
            g.DrawString(String.Format("{0:D2} d", now.Day), DateFont, DateInfo, new PointF(115F, 100F));
            g.DrawString(now.ToString("MMM"), DateFont, DateInfo, new PointF(115F, 120F));
            g.DrawString(now.ToString("yyyy"), DateFont, DateInfo, new PointF(115F, 140F));

            e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);
        }
        finally {
            g.Dispose();
            DateInfo.Dispose();
            background.Dispose();
            DateFont.Dispose();
            lineColor.Dispose();
        }
        //base.OnPaint(e);
    }

    protected override void OnPaintBackground(PaintEventArgs e) {
        //base.OnPaintBackground(e);
    }

    protected override void OnResize(EventArgs e) {
        if (_backBuffer != null) {
            _backBuffer.Dispose();
            _backBuffer = null;
        }
        base.OnResize(e);
    }

我认为通过在方法结束时处理所有东西我会很安全,但这似乎没有帮助。此外,运行时和 OutOfMemoryException 之间的间隔不是恒定的;一旦它只发生在几秒钟内,但通常需要一两分钟。下面是一些类范围的变量声明。

    private Bitmap _backBuffer;

    private float startAngle = -91F;
    private float brushWidth = 14;
    private float spaceStep = 6;

还有一个屏幕截图(编辑:屏幕截图链接到存在一些代码的视图):


(来源:ggot.org)

编辑:堆栈跟踪!

System.OutOfMemoryException: Out of memory.
   at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
   at System.Drawing.Graphics.DrawArc(Pen pen, Single x, Single y, Single width, Single height, Single startAngle, Single sweepAngle)
   at PolarClock.clockActual.OnPaint(PaintEventArgs e) in C:\Redacted\PolarClock\clockActual.cs:line 111
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

似乎与上次崩溃的同一行,循环内的主要drawArc。

【问题讨论】:

  • 请裁剪您的屏幕截图,使其不超过必要的大小。
  • @Gabe:完成;我使用较大的截图来显示一些额外的代码,所以我将新截图作为旧截图的链接。
  • 根据msdn.microsoft.com/en-us/library/…中的cmets,当弧i小于2像素或3.5扫描度时,drawArc中存在错误。
  • 我刚刚添加了 1.5 扫描度的规定,因为崩溃似乎发生在极小的值上,例如 0.02;我们会看看这是怎么回事
  • 提供的代码没有任何泄漏,在我的机器上运行稳定。

标签: c# gdi+ out-of-memory paint onpaint


【解决方案1】:

仅供其他人使用,通过 Google 找到此页面:

如果您使用 System.Drawing.DrawArc,可能会导致 System.OutOfMemoryException,如果您尝试打印小角度,也可能是错误。

对于角度

另见:

http://connect.microsoft.com/VisualStudio/feedback/details/121532/drawarc-out-of-memory-exception-on-small-arcs

【讨论】:

  • 谢谢,当它的距离小于 2 像素时,它有效地提示了如何使用 LinearGradientBrush 修复内存不足异常。
【解决方案2】:

确保您也处置 Pen 和 Brush 对象,并使用 using 块确保您处置对象,即使存在异常。

附带说明:避免在每次绘制时重新创建和处理 _backBuffer。要么捕获 resize 事件并在那里处置 _backBuffer,要么只检查 _backBuffer 在每个 Paint 事件上是否具有正确的尺寸,如果尺寸不匹配则处置并重新创建。

【讨论】:

  • 在这一点上,尺寸应该永远不会改变,但感谢您的提示!我会回去开始杀钢笔、画笔、字体等。
  • 正如 Fredrik Mörk 所说,不要忘记字体。当涉及到 GDI 对象时,Windows(和 .NET)确实很挑剔。始终释放所有可以被处理的 GDI 对象。
  • 并且不要处理e。这是调用该方法的代码的责任。
  • 我已经实施了所有这些建议,并更新了原始问题中的代码以反映这一点。我只是让它运行,看看它现在是否更好。五分钟后我会称它为暂时成功,如果它持续一整夜我会接受这个答案。
  • 如果您添加我在上面评论中提到的检查怎么办:不要绘制小于 3.5 度的弧?还有两个关于此错误的 Connect 报告:connect.microsoft.com/VisualStudio/feedback/details/253886/… 和 connect.microsoft.com/VisualStudio/feedback/details/121532/…。
【解决方案3】:

我没有发现您的代码有任何严重错误。您能否提供OutOfMemoryException 发生的确切行?

您知道,我确实花了几个月的时间才理解:OutOfMemoryException 并不意味着内存不足。 ;-) 它发生在 GDI+ 中,只是出现了问题(在 GDI 中显示错误的编码风格,恕我直言),例如您尝试加载无效图像或像素格式无效的图像等。

【讨论】:

  • 我会运行它直到它崩溃并发布一个堆栈跟踪。
  • 看起来很奇怪。它要么与您每 33 毫秒创建一个新对象的事实有关,要么与弧的某些计算产生无效结果(如 0)有关。尝试调试项目,再次等待崩溃并检查您传递给 DrawArc 的值!除此之外我不能给你任何建议:-(
  • 由于某种原因,当它在 IDE 中运行时崩溃,我没有查看异常详细信息的选项,我只是得到了无效的位图渲染,就是这样,没有停止,我的其他方法(如拖动和双击)工作正常。
  • 非常有趣。回到家后,如果问题没有解决,我会检查是否可以重现此错误!
  • 知道为什么我在 IDE 中没有收到异常通知吗?我很想在它崩溃时检查值...
【解决方案4】:

并不是为什么的真正答案,而是一个可能的解决方案:

您不应该每次都创建新的位图。 每次绘制新框架时清除它即可。

但是,当您的尺寸发生变化时,您应该创建一个新的位图。

【讨论】:

  • 在这一点上,尺寸应该永远不会改变,但感谢您的提示!
  • 哦,如果您将其更改为以这种方式工作,请注意最小化您的应用程序,您的控件可能会收到 0x0 的大小,并且不喜欢使用该大小创建位图;)
【解决方案5】:

为什么每次你想用 OnPaint 绘制东西时都需要一个新的位图?!你正好需要 1。试试这样的方法:

private Bitmap _backBuffer = new Bitmap(this.Width, this.Height);

protected override void OnPaint(PaintEventArgs e) { 

    Graphics g = Graphics.FromImage(_backBuffer);

    //Clear back buffer with white color...
    g.Clear(Color.White);

    //Draw all new stuff...
}

【讨论】:

  • 我最初这样做是为了确保我没有用完任何不必要的东西,尽管压倒性的共识似乎是这是一个坏主意;另一方面,如果画布改变大小,它可以重新创建,一旦我进行了一些自定义,这是可能的;在这种情况下,任何时候调整大小都需要重新制作。
【解决方案6】:

不是您的问题的答案,也许您这样做是有充分理由的(我可能会学到一些东西),但为什么要先创建位图,在位图上绘制,然后在表单上绘制位图? 直接在表格上画画不是更有效率吗? 类似这样的事情:

protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    //_backBuffer = new Bitmap(this.Width, this.Height);
    Graphics g = Graphics.FromImage(_backBuffer);

    //Rest of your code
    //e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);

    //g.Dispose();
    //e.Dispose();
    //base.OnPaint(e);

    //_backBuffer.Dispose();
    //_backBuffer = null;
}

也照MSDN

在派生类中重写 OnPaint 时,请务必调用基类的 OnPaint 方法,以便注册的委托接收事件。

【讨论】:

  • 非常感谢!有趣的材料。
猜你喜欢
  • 2011-05-07
  • 1970-01-01
  • 2021-12-13
  • 2015-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
相关资源
最近更新 更多