在使用WinForms绘制宽线时出现了OutOfMemoryException异常

3
这个问题很疯狂。我在OnPaint处理程序中画了几千条线,当pen.Width <= 1或屏幕上没有太多的线时,没有问题。
好吧,我画了一张缩放地图。线宽随地图缩放而变化。当我缩放某些地图时,会出现OutOfMemoryException。为什么?!
当我将pen.Width设置为1时,没有问题。当我将其设置为对应的轨道宽度时,有些地图可以正常绘制,但有些在特定缩放级别下会抛出异常。
到底发生了什么?这与实际内存使用无关。我已经反复检查过了。
顺便说一句,当它发生时,我设置的pen.Width大约为2。
代码看起来像foreach (...) g.DrawLine(...),在绘制几百条线后崩溃。
如果我找不到解决办法,我就必须放弃线宽缩放,这将大大降低演示的质量。或者我可以尝试一个丑陋的hack来捕获这个异常(如果可以捕获)...
注意:我不使用任何位图。我不操作巨大的数组。我在绘制过程中不打开任何文件。有一个向量数组(约10k个元素),我只是将它们全部作为单独的线条绘制,使用一些不同的笔来表示各种地图对象。当我没有触摸pen.Width时,不会出现异常。当我设置pen.Width时,有些地图在所有缩放级别下都可以正确显示,但有些会抛出异常。这5个笔在进入绘图循环之前在OnPaint事件中创建,并在退出循环后正确处理。在绘制每条线之前都会设置其宽度。
我尝试限制线坐标仅限于视口中实际可见的坐标。这是多余的,因为Graphics对象会自行处理。当然,这并没有帮助。我尝试在一些较小的窗口大小上进行操作-没有帮助。我尝试打开和关闭双缓冲。没有用。我已经没有更多的想法了。
编辑:
private void DrawMap(PaintEventArgs e) {
    var pens = new[] {
        new Pen(TrackColor),
        new Pen(SwitchColor),
        new Pen(RoadColor),
        new Pen(RiverColor),
        new Pen(CrossColor)
    };
    var b = Splines.Bounds;
    var g = e.Graphics;
    var f = true; // OutFull;
    var tr = GetTransformation();
    float ts = tr[0], tx = tr[1], ty = tr[2];
    TrackSpline[] visible = !f ? Splines.GetSubset(ts, _Viewport) : null;
    var ct = f ? Splines.Count : visible.Length;
    for (int i = 0; i < ct; i++) {
        TrackSpline s = f ? Splines[i] : visible[i];
        var pen = pens[s.T];
        pen.Width = ts * s.W;
        if (ts < 0.01 || s.L) {
            var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
            var p2 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
            g.DrawLine(pen, p1, p2);
        } else {
            var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
            var p2 = new PointF(s.B.X * ts + tx, s.B.Y * ts + ty);
            var p3 = new PointF(s.C.X * ts + tx, s.C.Y * ts + ty);
            var p4 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
            try {
                g.DrawBezier(pen, p1, p2, p3, p4);
            } catch (OutOfMemoryException) {
                g.DrawLine(pen, p1, p4);
            }
        }
    }
    foreach (var p in pens) p.Dispose();
}

看到这里的丑陋的hack了吗?它可以完美地工作,我甚至看不出哪些曲线被替换成了直线。显然,g.DrawBezier抛出了异常。我不喜欢丑陋的hack...


1
关于 OutOfMemoryException,请问你能分享完整的堆栈跟踪吗? - Quality Catalyst
1
请参阅GraphicsPath和OutOfMemoryException - LarsTech
这可能就是了!谢谢,伙计,我明天会检查一下,但这很有道理。 - Harry
你无法捕获内存不足异常 - catch (OutOfMemoryException) - 请参阅https://www.safaribooksonline.com/library/view/c-40-unleashed/9780132678926/h4_1619.html。 - Enigmativity
什么是 GetTransformation - TaW
我知道我无法捕获真正的 OutOfMemoryException - 但这个异常并不是很真实。我说过我已经仔细检查了,内存不是问题所在,而且我是正确的。可能是由于内部 .NET 错误引起了错误类型的异常。捕获真正的异常会导致应用程序不稳定或者后期出现非常丑陋的错误和崩溃。你指出这一点是好的。 - Harry
1个回答

4

这是解决方案,感谢 @LarsTech 的提示:

private void DrawMap(PaintEventArgs e) {
    var pens = new[] { // TODO: draw layers instead
        new Pen(TrackColor),
        new Pen(SwitchColor),
        new Pen(RoadColor),
        new Pen(RiverColor),
        new Pen(CrossColor)
    };
    var b = Splines.Bounds;
    var g = e.Graphics;
    var f = true; // OutFull; // (TODO: limiting vectors to visible ones)
    var tr = GetTransformation(); // gets scale and translation for points
    float ts = tr[0], tx = tr[1], ty = tr[2];
    TrackSpline[] visible = !f ? Splines.GetSubset(ts, _Viewport) : null;
    var ct = f ? Splines.Count : visible.Length;
    for (int i = 0; i < ct; i++) {
        TrackSpline s = f ? Splines[i] : visible[i];
        var pen = pens[s.T];
        pen.Width = ts * s.W;
        if (ts < 0.01 || s.L) {
            var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
            var p2 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
            g.DrawLine(pen, p1, p2);
        } else {
            var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
            var p2 = new PointF(s.B.X * ts + tx, s.B.Y * ts + ty);
            var p3 = new PointF(s.C.X * ts + tx, s.C.Y * ts + ty);
            var p4 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
            var b1c = Math.Abs(p1.X - p2.X) >= 0.1f || Math.Abs(p1.Y - p2.Y) > 0.1f;
            var b2c = Math.Abs(p3.X - p4.X) >= 0.1f || Math.Abs(p3.Y - p4.Y) > 0.1f;
            if (b1c && b2c) g.DrawBezier(pen, p1, p2, p3, p4); else g.DrawLine(pen, p1, p4);
        }
    }
    foreach (var p in pens) p.Dispose();
}

在他的链接答案中,我们读到:

这是由于钢笔和加宽方法出现了问题。确保您路径的起点和终点不相同。

是的,在.NET中存在bug,已向Microsoft报告,但显然尚未修复。而且在这里,贝塞尔曲线看起来太像直线了😉
我猜长度为零的线条可能会引发类似的异常。
请注意,我检查点坐标之间的距离大于0.1f,而不是0!这很重要。如果点彼此足够接近,就会引发异常,而不仅仅是当它们相等时。我可以计算点之间的距离,但出于性能原因最好不要这样做。
每个曲线和线条进行此类检查对性能并不利,但似乎比捕获虚假异常更好。检查可能可以优化一下,或移到"比例更改"处理程序中。
顺便说一句:我的代码中的GetTransformation()方法只是为所有点提供比例、X和Y偏移量。如果你想知道为什么我不使用内置的转换并手动完成它 - 那是因为内置的转换不能与双缓冲一起使用。这是.NET中的另一个错误还是特性?没有双缓冲绘图非常缓慢,所以必须在这里使用它。

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