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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With