4

In our application we have a few GDI+ objects that are used often in many different contexts. This includes some instances of Font, SolidBrush (White, Black...), some Pen...

For those objects our strategy so far has been to hold them through widely visible static read-only fields. This avoids to create/dispose them millions of time. We took care of thread-safety accesses on these object of course (they are just accessed from the UI thread basically).

There are just a few of these GDI+ objects hold for the lifetime of the application, say like 200. The others GDI+ objects (the ones with short-life) are all disposed asap. But we sometime get unexpected GDI+ resources outage exception, hopefully rarely enough.

I am wondering if these exceptions could come from these few GDI+ objects hold, and if this would be a wiser strategy to create/dispose tons of short-life GDI+ objects instead. Does anybody have real-world experience and relevant conclusions about theses two strategies?

Patrick from NDepend team
  • 13,237
  • 6
  • 61
  • 92
  • 1
    Is it possible that you are holding on to more GDI+ objects than you think and that the error is caused by too many GDI+ handles? – mbeckish Apr 02 '14 at 14:20
  • 1
    Also, instead of holding on to your own static objects, you could try using the ones provided by .NET, like [Pens.Black](http://msdn.microsoft.com/en-us/library/system.drawing.pens.black(v=vs.110).aspx) – mbeckish Apr 02 '14 at 14:24
  • [Here](http://stackoverflow.com/q/3532914/21727) are some [other](http://stackoverflow.com/q/420620/21727) links that talk about this issue. – mbeckish Apr 02 '14 at 14:26
  • It really sounds like you have a resource leak somewhere. Yes, there are limits to how many GDI+ resources you can hold at any one time but the count should be in the tens of thousands at least. – 500 - Internal Server Error Apr 02 '14 at 14:34
  • No we don't have leak, see my answer to Hans below. – Patrick from NDepend team Apr 02 '14 at 15:00

1 Answers1

13

Caching System.Drawing objects is a mistake. They are very cheap to create, very expensive to keep around. They are allocated on a special heap that all processes on a desktop need to share. The heap size is limited to 65535 objects. Creating objects like a brush or a pen takes roughly a microsecond, miniscule compared to the cost of the operation you perform with them. The only drawing object that is expensive is a Font. Creating one involves a sizable chunk of overhead taken by the font mapper. But that's already solved by .NET, it caches them.

Other than hogging the heap needlessly, the programming style is dangerous. It is far too easy to forget blindly applying the using statement. This certainly can get you into trouble, you are relying on the finalizer thread to release them again. If a program involves lots of heavy painting but not enough object allocation to trigger a GC then you will exhaust the quota for GDI objects. Which is 10,000 objects for a process by default. This will crash your program. Do note that the System.Drawing class wrappers are not nearly big enough to trigger a GC by themselves, 10000 of them will not be enough to get the finalizers to run and release the handles again.

The problem is very easy to diagnose, you can use Task Manager. Use View + Select Columns and tick "GDI Objects". Might as well tick "USER Objects", another one that's very easily leaked. Keep an eye on the displayed counts for your process while you are using it. A steadily climbing number spells doom.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Hans, thanks for this detailed answer, it looks like you coded Windows Form and GDI+ yourself, didn't you? ;) I will apply your advice and will report my experience here. Concerning # Handles so far I am monitoring it for years, and the outage resources exception still occurs, even on process with less than 1.000 objects. Hopefully, these exceptions are rare (I know coz we have a production exception remote logger). – Patrick from NDepend team Apr 02 '14 at 14:59
  • The Handles counter in Task Manager counts a very different kind of resource, they are kernel handles. Leaking them is possible but very rare. – Hans Passant Apr 02 '14 at 15:07
  • I agree with Hans; caching the objects is usually an mistake. If you are only caching a few objects, it is not always a mistake. Also, there is no harm in caching a few while rendering, and freeing them when rendering is completed. – Frank Hileman Apr 18 '14 at 17:22
  • I am actually in the process of caching GDI+ objects. So far it is working well except that I am stumbling on a situation where it is not practicable: when dealing with thousands of TreeNode that need various FontStyle (some are bold, some are underlined...) hence TreeNode.NodeFont are referencing Font objects cached. Does anyone imagine another way to proceed? Sure TreeNode could be custom drawn with short-living fonts, but this sounds to me overwhelming isn't it?! – Patrick from NDepend team May 20 '14 at 09:03
  • 1
    **Caching System.Drawing objects is a mistake** – Hans Passant May 20 '14 at 09:08
  • Hans, thanks for the follow up, but in the TreeNode like situation I exposed here yesterday, how would you do to not cache a set font objects (Regular/Bold/Italic...) referenced by thousands of TreeNode objects through the TreeNode.NodeFont properties? – Patrick from NDepend team May 22 '14 at 08:13
  • @PatrickfromNDependteam: We cache GDI+ objects in VG.net. The caches are limited, and the lifetime of each object is also limited. Some objects are cached for the duration of one rendering pass -- for those, we have only one per type (stroke pen, brush, etc), and they are deleted at the end. Other objects are cached for longer: texture brushes that are reused across rendering. In this case the caching made a huge difference in performance. In your case, you could do a sophisticated caching for the font objects, but it may not provide any benefit. Other types are more expensive to create. – Frank Hileman Jun 06 '14 at 17:48
  • @PatrickfromNDependteam: the caching strategy we use is quite complex but I could explain privately if you wish to contact me; I am on skype often with the name Frank Hileman. The first step though is to confirm that caching would have any benefit, as it may not. We do a similar caching with texture brushes. We use weak references in the cache and strong references from "originators". You must enforce a limit to the number of objects in the cache to avoid the problems Hans describes. – Frank Hileman Jun 06 '14 at 17:50
  • Thanks Frank, we did a huge refactoring to not cache GDI objects anymore, except in situation like the problem I exposed (Font obj referenced from thousands of TreeNode object must be cached). I'd be reluctant to use weak references for that. I am looking forward to see if what we've done will have a positive impact on the random GDI+ exception. – Patrick from NDepend team Jun 11 '14 at 13:52