【问题标题】:Double buffering throws exception at high load双缓冲在高负载时抛出异常
【发布时间】:2015-08-27 07:37:35
【问题描述】:

我正在测试我的 Windows 窗体用户控件。当应用程序负载很高时(例如应用程序占用超过 1 GB),它会抛出 Out of memoryArgument invalid 异常。

这里是用缓冲区绘制控件的代码,

protected override void OnPaint(PaintEventArgs e)
{
      if (m_buffer == null || m_Redraw)
      {
           if (m_buffer != null)
               m_buffer.Dispose();
            m_buffer = new Bitmap(Width, Height);

            using (Graphics g = Graphics.FromImage(m_buffer))
            {
                DrawUserControl(g, ClientRectangle);
            }
            m_Redraw= false;                
       }
       e.Graphics.DrawImage(m_buffer, Point.Empty);
       base.OnPaint(e);
}

内存不足异常发生在e.Graphics.DrawImage(m_buffer, Point.Empty);

new Bitmap(Width, Height);出现参数无效异常

注意:仅当应用程序负载超过 1 GB(例如 1.5 GB,2GB RAM)。

不带缓冲的绘制控件不会抛出任何异常但会导致闪烁效果。这是没有缓冲的代码

protected override void OnPaint(PaintEventArgs e)
{
     this.SuspendLayout();
     DrawUserControl(e.Graphics, ClientRectangle);
     this.ResumeLayout();
     base.OnPaint(e);
}

我希望我的控件在高负载下呈现而不会闪烁。我不希望应用程序由于我的控制而中断。请分享您的建议

与缓冲相关的编辑:

1) 缓冲区图像不会在所有绘制事件期间创建。如果需要重新绘制控件,它将在第一次创建。否则,将在控件上绘制现有的缓冲区图像。这样可以避免不必要的控件重绘

2) 如果控件需要重新绘制,将使用新的位图,因为现有缓冲区图像的大小和控件的当前大小可能会有所不同。即使我使用现有图像,参数无效抛出异常

尝试使用 BufferedGraphicsContext

根据@taffer 的回答,我尝试使用BufferedGraphicsContext。它还会引发内存不足异常。我在下面发布堆栈跟踪

System.ComponentModel.Win32Exception (0x80004005):存储空间不足 可用于处理此命令 在 System.Drawing.BufferedGraphicsContext.CreateCompatibleDIB(IntPtr hdc, IntPtr hpal, Int32 ulWidth, Int32 ulHeight, IntPtr& ppvBits)

在 System.Drawing.BufferedGraphicsContext.CreateBuffer(IntPtr src, Int32 offsetX, Int32 offsetY, Int32 width, Int32 height)

在 System.Drawing.BufferedGraphicsContext.AllocBuffer(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)

在 System.Drawing.BufferedGraphicsContext.AllocBufferInTempManager(图形 targetGraphics, IntPtr targetDC, 矩形 targetRectangle) 在 System.Drawing.BufferedGraphicsContext.Allocate(Graphics targetGraphics, Rectangle targetRectangle)

【问题讨论】:

  • 尽量避免一次全部加载?部分加载并与启动画面结合?
  • 您不需要在每次绘制时都处理和重新创建缓冲区。清除它。
  • 创建一个位图是昂贵的 ..你这样做,可能每秒数百次。将缓冲区分配到任何高流量区域之外。
  • 此外,大多数控件都有一个双缓冲标志,您可以通过 Control.SetStyle 在 .ctor 中设置。
  • 旁注:SuspendLayout()ResumeLayout()与绘画无关。绘制控件不应该触发LayoutEvent,否则你的控件会很慢。您应该删除这些行。

标签: c# winforms out-of-memory doublebuffered


【解决方案1】:

首先,为什么在OnPaint 中调用SuspendLayoutResumeLayout?仅当容器控件(例如 Panel)包含具有停靠/对齐功能的子控件并且您希望防止在多个步骤中调整容器大小时自动重新对齐时才需要。

其次,双缓冲有很多更好的内置方法。

选项 1:

对于Forms、Panels 和其他常用控件,在大多数情况下,设置DoubleBuffered 属性就足够了。它是受保护的,所以你应该创建一个派生类:

public class DoubleBufferedPanel : Panel
{
    public DoubleBufferedPanel()
    {
        DoubleBuffered = true;
    }
}

选项 2:

致电SetStyle:

public class DoubleBufferedPanel : Panel
{
    public DoubleBufferedPanel()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
    }
}

选项 3:

在某些情况下无法使用上述选项(例如,Windows 的淡入淡出动画不能与双缓冲一起使用)。这里可以使用OnPaint中的BufferedGraphics类:

protected override void OnPaint(PaintEventArgs e)
{
    // creating a buffered context
    using (BufferedGraphicsContext context = new BufferedGraphicsContext())
    {
        // creating a buffer for the original Graphics
        Rectangle rect = new Rectangle(Point.Empty, Control.ClientSize);
        using (BufferedGraphics bg = context.Allocate(e.Graphics, rect))
        {
            using (PaintEventArgs be = new PaintEventArgs(bg.Graphics, e.ClipRectangle))
            {
                // Draw your control here onto the buffer (applied to your post)
                DrawUserControl(be.Graphics, rect);
                base.OnPaint(be);
            }

            // copying the buffer onto the original Graphics
            bg.Render(e.Graphics);
        }
    }
}

【讨论】:

  • 感谢您的回答,选项 1 和 2 将没有用,因为我的控件是从 Control 类派生的。我会尝试选项 3
  • 选项 3 也抛出异常,我将在我的问题中添加详细信息
猜你喜欢
  • 1970-01-01
  • 2020-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-24
  • 1970-01-01
  • 2012-12-24
相关资源
最近更新 更多