This is a follow-on to this previous post, prompted by a thread over on The Swamp discussing a recent blog post by Fenton Webb on the AutoCAD DevBlog.
Fenton’s assertion is that you really need to call Dispose() on all AutoCAD objects that you create yourself, unless they are managed by AutoCAD’s transaction system (i.e. you’ve passed responsibility across to AutoCAD by calling Transaction.AddNewlyCreatedDBObject()). Which means that while you don’t need to call Dispose() on objects such as the AutoCAD Editor or the active Document (and you really shouldn’t), you really should call Dispose() on various objects you’ve been used to letting the .NET garbage collector (GC) dispose of, such as those belonging to the Autodesk.AutoCAD.Geometry namespace.
In my previous post on this topic, I said:
“The first category of temporary objects, such as Geometry.Line [sic – I obviously meant Geometry.Line3d], are safe to be disposed of either "manually" (by your own code) or "automatically" (by the .NET garbage collector).”
As Fenton says, the word “safe” isn’t strictly true: while in practice it’s safe for the majority of classes belonging to the Autodesk.AutoCAD.Geometry namespace, in theory some of them could cause problems if finalized by a thread other than the UI thread. This happens when a class implementation relies on some kind of shared resource – or modifies shared state in some way – in which case it’s quite likely to release the resource/modify the state when destroyed. And if this should happen from a thread other than the main thread (and the GC runs on a background thread by default in .NET) then bad things happen. Which usually means AutoCAD crashes.
Rather than tell developers “you really need to go and modify all the AutoCAD .NET code you’ve written over the last 8-9 years to call Dispose() every time you create such an object”, I thought it would be more helpful to analyse the risks a little more closely.
Before going into the analysis, I would say that at this stage it should be considered best practice to dispose of all temporary AutoCAD .NET objects – however simple they may appear – explicitly from your code. This can be done via the Dispose() method or via a using() block, of course. It certainly would continue to seem “safe” to leave certain objects to the GC, but then you never really know when and where complexity might inadvertently be added.
I had always felt that Autodesk.AutoCAD.Geometry was pretty safe, in this regard(hence my post of 4 years ago), but it turns out the namespace contains a few corner cases, even now, that could easily cause you problems.
To analyse the extent of the problem, I started by grepping our internal source for AcGe classes with destructors. I found 18 such internal classes, of which 11 were exposed via the ObjectARX layer (i.e. there were AcGe equivalents for them).
Here are those classes. Not all of their destructors actually do anything very complicated – most of the classes only maintain local object state, for instance, which means they can effectively (today) have their managed counterparts’ finalization left to the GC (for those that have them – not all of these classes are exposed via .NET, either) – but there are some that are altogether riskier.
- AcGeCurveBoundary
- AcGeCurveSurfInt
- AcGeSurfSurfInt
- AcGeNurbCurve3d
- AcGeOffsetCurve3d
- AcGePointOnCurve3d
- AcGePointOnSurface
- AcGeTrimmedSurface
- AcGeExternalCurve2d
- AcGeExternalCurve3d
- AcGeExternalSurface
- AcGeExternalBoundedSurface
Now I haven’t stepped through each of these classes’ destructors in the debugger to see which are genuinely problematic, but I can say that particular care needs to be taken with the ones in bold. They essentially form a connection between the (otherwise fairly self-contained) geometry library and the BRep API inside AutoCAD: they exist to maintain links to externally-defined geometry, such as that contained within the ASM (Autodesk Shape Manager) sub-system.
And – sure enough – Stephen Preston tells me that the ADN team (and AutoCAD Engineering) have had to track down issues related specifically to not calling Dispose() on objects created by AutoCAD’s BRep API, in the past.
So, to summarise… I don’t suggest people necessarily go back through and systematically call Dispose() on every geometry object they’ve ever created within their AutoCAD .NET code, but it’s certainly worth paying attention to this, moving forwards, as well as keeping in mind the problematic areas (such as BRep) should unexplained crash reports start to come in from users.