What is the best practise when using ComReleaser?

5523
15
Jump to solution
01-23-2013 06:05 AM
DuncanHornby
MVP Notable Contributor
Coding gurus,

I've got a question about best practise when using the ComReleaser object. In the first example below is some stub code showing how I would typically using ComReleaser when I'm processing within a single loop.

Using comRel As New ComReleaser()     pFeatureCursor = pFeatureClass.Update(pQueryFilter, False)   comRel.ManageLifetime(pFeatureCursor)   pFeature = pFeatureCursor.NextFeature    Do While Not pFeature Is Nothing    ' Do something  pFeatureCursor.UpdateFeature(pFeature)     pFeature = pFeatureCursor.NextFeature   Loop   pFeatureCursor.Flush() End Using


Now suppose I want to loop through another cursor whilst looping through the first cursor, so nested. Is it appropriate to have a Using statement within a using statement (1) or do you simply add the inner cursor to the comReleaser object (2). I ask as I've not seen any examples and was wondering what was the best approach when nesting cursors?

Example 1


Using comRel As New ComReleaser()     pFeatureCursor = pFeatureClass.Update(pQueryFilter, False)   comRel.ManageLifetime(pFeatureCursor)   pFeature = pFeatureCursor.NextFeature    Do While Not pFeature Is Nothing    ' Do something    Using comRel2 as New ComReleaser()       pFeatureCursor2 = pFeatureClass2.Search(pQueryFilter2, False)       comRel2.ManageLifetime(pFeatureCursor2)       ' Do something inner looping stuff  End Using    pFeatureCursor.UpdateFeature(pFeature)     pFeature = pFeatureCursor.NextFeature   Loop   pFeatureCursor.Flush() End Using


Example 2

Using comRel As New ComReleaser()     pFeatureCursor = pFeatureClass.Update(pQueryFilter, False)   comRel.ManageLifetime(pFeatureCursor)   pFeature = pFeatureCursor.NextFeature    Do While Not pFeature Is Nothing    ' Do something     pFeatureCursor2 = pFeatureClass2.Search(pQueryFilter2, False)       comRel.ManageLifetime(pFeatureCursor2)     ' Do something inner looping stuff    End Using    pFeatureCursor.UpdateFeature(pFeature)     pFeature = pFeatureCursor.NextFeature   Loop   pFeatureCursor.Flush() End Using


In example 2  pFeatureCursor2 is added to comRel on every cycle of the outer cursor loop, is that good, bad or irrelevant?
0 Kudos
1 Solution

Accepted Solutions
JasonPike
Occasional Contributor
Neil and Jason,

Thank you for telling me why I should not be using comReleaser! ESRI use it in many of their code examples so this is why I have adopted it as I understood it guaranteed the release of the cursor but as Jason shows that is not always the case and using Marshal.ReleaseComObject is a more robust solution.

BUT, I can't help noticing this does not answer my question...;)

If you are using comReleaser what is the best practise when you have nested loops? Neil points out it is an ESRI class, may be one the the ESRI product developers can answer this question?

Duncan


Duncan,

Your second example is the best answer--you only need one instance of the ComReleaser.
[CORRECTION: The second example is not the best answer--see Neil's response about making sure cursors are released inside the loop.]

However, you may only end the using block once. I think the "End Using" in the inner loop that I highlighted in red was a cut and paste mistake.

Using comRel As New ComReleaser()     pFeatureCursor = pFeatureClass.Update(pQueryFilter, False)   comRel.ManageLifetime(pFeatureCursor)   pFeature = pFeatureCursor.NextFeature    Do While Not pFeature Is Nothing    ' Do something     pFeatureCursor2 = pFeatureClass2.Search(pQueryFilter2, False)       comRel.ManageLifetime(pFeatureCursor2)     ' Do something inner looping stuff     End Using    pFeatureCursor.UpdateFeature(pFeature)     pFeature = pFeatureCursor.NextFeature   Loop   pFeatureCursor.Flush() End Using


[EDIT: The paragraph below is still relevant, but Neil's response points out that releasing the COM object isn't the point--the point is having that COM object close the table it has opened, which only happens when it is released.]

The reason why you would want to add pFeatureCursor2 to the ComReleaser instance for each iteration of the inner loop is because every time you assign pFeatureCursor2 to the output of the Search() call pFeatureCursor2 is pointed to a different instance of IFeatureCursor. This means that under-the-hood a new instance of a COM object was created and returned to you and assigned to pFeatureCursor2. If you want that instance of the COM object to go away when the using block ends, you have to add it to the ComReleaser (or use the other approaches suggested by me and Neil.)

View solution in original post

0 Kudos
15 Replies
NeilClemmons
Regular Contributor III
IMO, the best practice is not to use ESRI's ComReleaser class at all.  The best way to manage your COM objects is to do it yourself so that you know what's going on and have full control over it.  When looping through a cursor, you should also be releasing the row/feature objects within the loop before assigning the reference to the next item in the cursor.

Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, False)
Dim feature As IFeature = featureCursor.NextFeature
Do While feature IsNot Nothing
  ' do something with the feature

  Marshal.ReleaseComObject(feature)
  feature = featureCursor.NextFeature
Loop

Marshal.ReleaseComObject(featureCursor)
0 Kudos
JasonPike
Occasional Contributor
I second what Neil said. I would add that you probably want to release in a finally-block so that it is guaranteed to happen.

I've copied my reply from a different forum thread (http://forums.arcgis.com/threads/75186-Memory-leak-with-FeatureClass-object). It is probably way more than you were asking, but the short answer, in my opinion, is don't use ComReleaser:

COM objects do not reside in managed memory; they reside on the unmanaged heap.
COM objects are used in .NET through proxies called Runtime Callable Wrappers (RCWs) that reside on the managed heap.

COM objects use reference counting to determine when they can be deallocated. They maintain a count of references to themselves and deallocate when that counter reaches 0. When a reference to a COM object is made in .NET, a RCW is created an the COM object is incremented once and only once for that instance.

RCWs also maintain a reference count. It is separate from the underlying COM object's reference count; it tracks the number of times IUnknown is marshalled from the unmanaged heap to the managed heap. When the RCW's reference count reaches zero, it detachs itself from the COM object which causes the COM object's reference to decrement by 1.

The most important thing to remember is that for an instance of an object (e.g. a layer object containing rivers, a feature class object containing cities, etc.) there is only one, shared RCW per application domain. If that RCW is detached from the COM object, then it is detached for every managed (read: .NET) object in that application domain, including .NET code that you didn't write and have no control over. Unless you've created your own application domain, every COM object you reference in your managed code will be loaded into the Shared application domain (1 of 3 application domains created by default for each .NET process) and shared with the rest of the managed code in the process.

Microsoft provides two methods for manually releasing COM objects for cases where deterministic memory management is required and the non-deterministic .NET garbage collector cannot be relied upon:
Marshal.ReleaseComObject - decrements the RCW's reference count by 1
Marshal.FinalReleaseComObject - decrements the RCW's reference count until it reaches zero and detaches the underlying COM object

Code should only ever call ReleaseComObject as many times as it has incremented the reference count. If the RCW is released too many times, it can detach the underlying COM object before other objects that reference it are finished with it. When this happens, you get the very familiar: "COM object that has been separated from its underlying RCW cannot be used."

FinalReleaseCom object should only be used when the caller is absolutely sure no other objects are referencing the COM object--this includes managed code in the application domain that you may not have created.

As noted in other threads, ComReleaser essentially calls Marshal.FinalReleaseComObject for every object you've told it to manage. This means that you had better be sure that no other managed code in the application domain (your product, other 3rd parties, etc.) is holding a reference to the RCWs you've told it to manage. Since you can't know what other managed code might be running in the process, I think that it is unsafe to use ComReleaser--instead, COM interop should be understood and applied carefully using ReleaseComObject appropriately.

I understand that COM interop is an advanced programming topic and that the ComReleaser class was meant to lessen the pain for developers that didn't want to take the time learn it. However, this is when ComReleaser is most dangerous--when it is used by developers who don't understand its limitations and ramifications.

I've had bugs filed against products I've developed only to find out that they only occurred when another product was installed alongside it and which eventually led to the discovery that the other product had mismanaged RCWs that our product happened to be using also.

So, all that to say: ComReleaser is useful if you are absolutely sure you're passing it objects that are not referenced anywhere else in the application domain; otherwise, you risk the stability of the program when you use it.

Resources:
http://www.amazon.com/NET-COM-Comple.../dp/067232170X
http://www.amazon.com/Advanced-NET-D.../dp/0321578899
http://msdn.microsoft.com/en-us/libr...v=vs.110).aspx
http://en.wikipedia.org/wiki/Reference_counting
http://msdn.microsoft.com/en-us/library/8bwh56xe.aspx
0 Kudos
DuncanHornby
MVP Notable Contributor
Neil and Jason,

Thank you for telling me why I should not be using comReleaser! ESRI use it in many of their code examples so this is why I have adopted it as I understood it guaranteed the release of the cursor but as Jason shows that is not always the case and using Marshal.ReleaseComObject is a more robust solution.

BUT, I can't help noticing this does not answer my question...;)

If you are using comReleaser what is the best practise when you have nested loops? Neil points out it is an ESRI class, may be one the the ESRI product developers can answer this question?

Duncan
0 Kudos
JasonPike
Occasional Contributor
Neil and Jason,

Thank you for telling me why I should not be using comReleaser! ESRI use it in many of their code examples so this is why I have adopted it as I understood it guaranteed the release of the cursor but as Jason shows that is not always the case and using Marshal.ReleaseComObject is a more robust solution.

BUT, I can't help noticing this does not answer my question...;)

If you are using comReleaser what is the best practise when you have nested loops? Neil points out it is an ESRI class, may be one the the ESRI product developers can answer this question?

Duncan


Duncan,

Your second example is the best answer--you only need one instance of the ComReleaser.
[CORRECTION: The second example is not the best answer--see Neil's response about making sure cursors are released inside the loop.]

However, you may only end the using block once. I think the "End Using" in the inner loop that I highlighted in red was a cut and paste mistake.

Using comRel As New ComReleaser()     pFeatureCursor = pFeatureClass.Update(pQueryFilter, False)   comRel.ManageLifetime(pFeatureCursor)   pFeature = pFeatureCursor.NextFeature    Do While Not pFeature Is Nothing    ' Do something     pFeatureCursor2 = pFeatureClass2.Search(pQueryFilter2, False)       comRel.ManageLifetime(pFeatureCursor2)     ' Do something inner looping stuff     End Using    pFeatureCursor.UpdateFeature(pFeature)     pFeature = pFeatureCursor.NextFeature   Loop   pFeatureCursor.Flush() End Using


[EDIT: The paragraph below is still relevant, but Neil's response points out that releasing the COM object isn't the point--the point is having that COM object close the table it has opened, which only happens when it is released.]

The reason why you would want to add pFeatureCursor2 to the ComReleaser instance for each iteration of the inner loop is because every time you assign pFeatureCursor2 to the output of the Search() call pFeatureCursor2 is pointed to a different instance of IFeatureCursor. This means that under-the-hood a new instance of a COM object was created and returned to you and assigned to pFeatureCursor2. If you want that instance of the COM object to go away when the using block ends, you have to add it to the ComReleaser (or use the other approaches suggested by me and Neil.)
0 Kudos
NeilClemmons
Regular Contributor III
The Using statement scopes the resource (the variable you declare) to the block of code between the Using and End Using statements.  The purpose of the Using statement is to limit the scope of a resource within a larger block of code and to guarantee that it is disposed as soon as the End Using statement is executed.  So, is it ok to nest Using statements?  Yes.  It's really no different than nesting loops or If/End If blocks.  In your case, you're using a cursor within a loop.  Each loop iteration opens a new cursor.  That cursor needs to be released with each loop iteration.  Each time a cursor is created, a table in the database is opened.  It will not be closed until the cursor is released, so if you don't release the cursor within each loop iteration it will keep opening tables and not closing them.  Eventually, you'll hit the max. number of open tables and an exception will be thrown.  So, that cursor needs to be managed by its own ComReleaser object declared within the loop.  If you use the ComRelease declared outside of the loop then the cursors created inside the loop will not be released until after the loop is exited.
0 Kudos
JasonPike
Occasional Contributor
The Using statement scopes the resource (the variable you declare) to the block of code between the Using and End Using statements.  The purpose of the Using statement is to limit the scope of a resource within a larger block of code and to guarantee that it is disposed as soon as the End Using statement is executed.  So, is it ok to nest Using statements?  Yes.  It's really no different than nesting loops or If/End If blocks.  In your case, you're using a cursor within a loop.  Each loop iteration opens a new cursor.  That cursor needs to be released with each loop iteration.  Each time a cursor is created, a table in the database is opened.  It will not be closed until the cursor is released, so if you don't release the cursor within each loop iteration it will keep opening tables and not closing them.  Eventually, you'll hit the max. number of open tables and an exception will be thrown.  So, that cursor needs to be managed by its own ComReleaser object declared within the loop.  If you use the ComRelease declared outside of the loop then the cursors created inside the loop will not be released until after the loop is exited.


Agreed. I have updated my post.
0 Kudos
LeoDonahue
Occasional Contributor III
IMO, the best practice is not to use ESRI's ComReleaser class at all.  The best way to manage your COM objects is to do it yourself so that you know what's going on and have full control over it.  When looping through a cursor, you should also be releasing the row/feature objects within the loop before assigning the reference to the next item in the cursor.

Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, False)
Dim feature As IFeature = featureCursor.NextFeature
Do While feature IsNot Nothing
  ' do something with the feature

  Marshal.ReleaseComObject(feature)
  feature = featureCursor.NextFeature
Loop

Marshal.ReleaseComObject(featureCursor)


Is feature a managed or unmanaged object?

If it is a managed object, wouldn't the fact that the previous feature reference make it eligible for garbage collection in .NET?  Otherwise, I agree, you should release it.

http://msdn.microsoft.com/en-us/library/ee787088.aspx#manipulating_unmanaged_resources
0 Kudos
JasonPike
Occasional Contributor
Is feature a managed or unmanaged object?

If it is a managed object, wouldn't the fact that the previous feature reference make it eligible for garbage collection in .NET?  Otherwise, I agree, you should release it.

http://msdn.microsoft.com/en-us/library/ee787088.aspx#manipulating_unmanaged_resources


It is managed in the sense that the interfaces are referenced from a managed PIA, but the underlying implementation is in COM.

In fact, you can't directly use COM objects, you use an RCW, which is managed and eligible for garbage collection, to interact and marshal data back and forth between your .NET code and COM code. The RCW will eventually be collected by the .NET GC and will release the COM object. But, the problem is that it is done at the whim of the GC and not immediately. In this case, the cursor needs to be released immediately because it has a table open, so we can't wait around on the GC to do its thing. Rather than waiting on the GC, you can tell the RCW to let go of the COM object by calling Marshal.ReleaseComObject on it. The RCW sticks around and waits to be collected by the GC, but it disconnects itself from the COM object it was helping you communicate with.
0 Kudos
LeoDonahue
Occasional Contributor III

In fact, you can't directly use COM objects, you use an RCW, which is managed and eligible for garbage collection, to interact and marshal data back and forth between your .NET code and COM code. The RCW will eventually be collected by the .NET GC and will release the COM object. But, the problem is that it is done at the whim of the GC and not immediately.

Same issue in Java.  In Java, we can call garbage collection methods, but garbage collection will run when it wants according to whatever algorithm is uses.

  In this case, the cursor needs to be released immediately because it  has a table open, so we can't wait around on the GC to do its thing.  Rather than waiting on the GC, you can tell the RCW to let go of the COM  object by calling Marshal.ReleaseComObject on it. The RCW sticks around  and waits to be collected by the GC, but it disconnects itself from the  COM object it was helping you communicate with.

So you're saying that your code will not wait for garbage collection, why is that?  Because of the nested cursors it is running slow, or ?
0 Kudos