我应该在每个绘画请求中创建新的笔刷/画笔,还是在整个应用程序生命周期内保留它们?

18

我有一个应用程序,它进行了很多绘图,假设它是一个类似于Visio的应用程序。它有一些具有多个子对象的对象,这些对象可以被绘制,连接,调整大小等。目前,当我对特定的子对象或对象调用paint方法时,我会执行以下操作:

using(var pen = new Pen(this.ForeColor))
{
    // Paint for this object.
}

我看到了不同的答案,有人说在一个不断绘制相同内容的应用程序中(可能只是调整大小、移动等),应该这样做。我应该将 Pen/Brush 与对象一起存储,并在应用程序被处理时全部释放它们,还是创建/释放每个绘画调用所需的效率足够高(记住这是一个相当图形密集型的应用程序)。

编辑:已经有两个答案提供了相互矛盾的答案,这就是我不确定是否要进行切换的地方。有没有人对差异有任何统计数据?

5个回答

9

当然,你也可以使用Pens和Brushes类,这些类提供了由运行时已经创建的对象。

例如,如果你想要一个标准颜色的Pen,可以这样做:

var pen = Pens.Red;

同样地,如果你只需要标准的纯色画笔,你可以使用Brushes来完成相同的操作:
var brush = Brushes.Red

使用这些工具,您不需要担心清理、处理或其他方面的问题。
如果您想要创建自己的不同颜色,例如使用不同的 alpha 组件或渐变刷子,则仍然需要自己创建并适当清理它们。
编辑:在我的古老的 XP 机器上运行测试应用程序,在创建和处理 100,000 个新笔的数组大约需要半秒钟的时间(在 Debug 模式下)。
这相当于每支笔大约 5 微秒。只有您可以决定这是否足够快。我猜测这段时间可能与您的其他操作相比相对不重要。

是的,所有笔刷/画笔的颜色都不同于预定义的颜色。 - TheCloudlessSky
我在查看你的帖子之前进行了一些测试,你是正确的,创建/处理非常快。在这种情况下,优化是不必要的。谢谢! - TheCloudlessSky

3
只有在您特定的应用程序上进行测试后,才能知道性能影响,但是该框架似乎没有问题,可以在应用程序的整个生命周期中保留一些笔。第一次调用 Pens.Black 时,它会创建一个黑色笔并将其缓存。您将在以后的调用中获得相同的对象,并且它从未被明确地处理(实际上,Pens.Black.Dispose() 将引发异常)。但是,您不希望盲目地创建笔,并在应用程序结束时将它们保留待处理,因为这会泄漏非托管内存。根据应用程序中使用模式的不同,有几个选择可供选择。
给每个对象一个私有的 Pen,在设置 ForeColor 时创建并重复使用以进行所有绘画。您应该使您的对象 IDisposable,以便可以正确地处理该 Pen。
如果您使用的颜色相对较少,但许多对象使用每种颜色,则可能不希望每个对象拥有自己的 Pen。创建一些缓存类来保持 Dictionary<Color,Pen> 并通过 PenCache.GetPen(ForeColor) 分发它们。就像使用 Pens.Black 一样,现在您可以忘记处理它们了。如果您短暂地使用一种颜色,然后不再需要它,则会出现问题。Pen 被缓存,因此您将永远占用它的内存。相反,您可以使用 Dictionary<Color,WeakReference<Pen>>,允许缓存的笔在不再需要时最终进行垃圾回收。
最后一个选项可能是最好的通用解决方案,避免了不必要的创建和处理,同时让垃圾收集器确保孤立的笔不会造成太多内存麻烦。当然,在您特定的情况下,它可能并不更好。

+1,因为我真的很喜欢使用WeafReference的想法。我将不得不在另一个项目中阅读更多相关信息。通过一些测试,我发现创建/释放笔刷是相当高效的,所以我接受了安迪的答案。 - TheCloudlessSky
我不知道这是否值得。实际上,笔具有丰富的选项,不仅仅是普通的颜色。宽度、画笔、样式、图案、帽子等等。如果你需要它们全部,那将会很麻烦。 - GorillaApe

2
重新使用在多次操作中需要的刷子,比每次使用using处理它们要快得多。
我肯定会建议尽可能多地重用它们(我之前看过代码比较,但现在找不到来源),但确实要记住在真正完成时将它们处理掉。

1
你没有任何证据表明速度提升了多少?如果不是真正必要的话,我不想进行优化。 - TheCloudlessSky

0

是的,最好像@fantius建议的那样进行测试,你可能会发现它并不重要 - 虽然我会倾向于保持它们开放,因为如果我记得正确,它们是未受管理的资源,可能会占用您的内存。 如果您每次都重新创建它们,您需要特别小心地正确处理它们以避免内存泄漏。


每次重新创建时,可以使用“using”语句将它们处理掉。 - TheCloudlessSky

0

好的,这是一个旧问题,对于一些人来说可能是新问题,它有一个简单直观的解决方案,并附有详细的解释。

“我应该比绘画操作所需的时间更长地保留绘画资源,例如笔和刷子吗?”这个问题被重新表述了吗?答案是否定的,你不应该在那种情况下这样做;但为什么呢,这意味着什么?

当你向图形对象绘制时,每创建一个绘画对象(如笔、刷子、路径、矩阵等)都会使用占用内存的资源。

是的,确实需要创建笔、刷子等进行绘画,然后执行myPen.dispose(),紧接着立即释放所有对已处理对象的引用,通过将该对象树设置为null(例如:myPen = null;),这样可以使垃圾回收器释放这些对象持有的非托管内存,而无需等待调用对象finalize()。请参见:Garbage collection 以获取有关C#中垃圾回收工作原理的更多信息。

创建太多的这些IDisposable类对象,并且在使用它们后不释放这些对象,可能会对程序的运行造成严重影响,例如可能导致堆栈和堆内存异常,由于必须排序大量不必要的对象而导致增加CPU使用率,潜在地导致性能变慢甚至运行时崩溃,如果不加检查就会发生。有关更多信息,请参见 Stack and Heap

故事的寓意是释放不再需要的资源;如果您必须保留资源,请在“保留资源”时进行基准测试,希望避免负面后果。

经验法则:最外层要确保在退出“绘制事件”例程时释放所有资源。最好的方法是,在每个绘制事件方法完成时,由该方法隐式创建的任何资源也应该释放。罚款,任何传递给这些方法的对象引用(如笔和画刷等)将被保留,直到调用基本对象finalize,这比必要的时间长,可以被认为是内存泄漏在定义的一般术语中。*传递太多引用等于无法使用的内存时间比预期或假定的时间长。

注意:谨慎地将像笔和画刷这样的对象引用传递给方法,并在类级别实现IDisposable接口以执行绘画操作。你的作业变得更加昂贵,建议查看IDisposable接口
祝你绘画愉快!

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