What's Up with ComReleaser? Is it really working?

9939
20
05-11-2010 02:19 PM
DavidPlume1
New Contributor III
Hi Everyone,

We're using the ComReleaser object in .NET to free up cursors, feature cursors and a few other COM objects in various functions through out our project.

The following code shows a typical implementation. We're finding that a number errors are being thrown in our code when it is running suggesting that the releaser is not working the way we think that i might be.

I am under the impression that the ComReleaser object "knows" to clean up as it gets to the "End Using" statement. Apparently not?

Errors that we're seeing are:

COM object that has been separated from its underlying RCW cannot be used.
Too many tables open.
Attempted to read or write to protected memory.

Bottom line, what is the best way to ensure that COM objects are cleaned up appropriately?

Thanks
D


Here is a typical implementation:

Using pComReleaser As ComReleaser = New ComReleaser

Try

Dim pBreakptFC As ESRI.ArcGIS.Geodatabase.IFeatureClass
Dim pQF As ESRI.ArcGIS.Geodatabase.IQueryFilter

Dim pTable As ESRI.ArcGIS.Geodatabase.ITable = Nothing
pComReleaser.ManageLifetime(pTable)

Dim pFeatCursor As ESRI.ArcGIS.Geodatabase.IFeatureCursor = Nothing
pComReleaser.ManageLifetime(pFeatCursor)

Dim pFeat As ESRI.ArcGIS.Geodatabase.IFeature

pBreakptFC = GetBreakptClass(MXApp)
pFeatCursor = Nothing

If Not pBreakptFC Is Nothing Then

pTable = pBreakptFC
pQF = New ESRI.ArcGIS.Geodatabase.QueryFilter

pQF.WhereClause = FIELD_PERIMETERID & " = " & CStr(PerimFeat.OID)

If pTable.RowCount(pQF) > 0 Then

pFeatCursor = pBreakptFC.Search(pQF, False)
pFeat = pFeatCursor.NextFeature
Do While Not pFeat Is Nothing
pFeat.Delete()
pFeat = pFeatCursor.NextFeature
Loop

End If

End If

pBreakptFC = Nothing
pQF = Nothing
pTable = Nothing
pFeat = Nothing
pFeatCursor = Nothing

Catch ex As Exception
LogError(LIBRARY_NAME, ex.ToString())
End Try

End Using
0 Kudos
20 Replies
KirkKuykendall
Occasional Contributor III
Hey Neil -

Thanks for responding.  This is helpful.

I'm starting to see how this would be a hard thing to track down.  I guess instead of keeping a reference to the IFeature, I could hang onto its' OID, then have a method in the DAL that:

  • puts the BO's into a Dictionary<int,BO>

  • Gets a cursor by passing the array of Dictionary keys to IGeoDatabaseBridge2.GetFeatures

  • loops through the cursor, synching each feature with Dictionary[IFeature.oid]

  • Release feature

  • Release Cursor

0 Kudos
DavidPlume1
New Contributor III
Thanks again everyone, I'm finding the whole conversation very instructive and hope that others will also find it so in the future. 

Here, in our current project, It seems to me that our best strategy now will be to change our coding to the patterns as James suggested, still using ComReleaser.   Furthermore we'll review our code to confine the use of the releaser to the basics that James also cited.

Best Regards.
0 Kudos
DavidPlume1
New Contributor III
For the sake of the thread, the following links provide a bit more insight into what is happening relative to releasing COM objects and the role of the Using block.

Bottom line, my take is this:  unlike VB6, setting COM objects = nothing doesn't really buy very much.  When your functions and subs go out of scope your objects and their respective wrappers lurk around in object purgatory waiting for the garbage collector to clean them up.  Generally speaking, this isn't a problem but for some resources it is best to explicitly force the release so one doesn't tie up limited resources such as tables.

Hence, Marshal.ReleaseComObject(pMyCOMObject) --> I think of this sort of like pMyCOMObject = nothing in VB6

See also:
http://www.getdotnetcode.com/gdncstore/free/Articles/ReleasingComObjectsWithVbNet.htm

For those interested in the ComReleaser and the role of the Using statement see:

http://msdn.microsoft.com/en-us/library/htd05whh.aspx

In part from that article:

Sometimes your code requires an unmanaged resource, such as a file handle, a COM wrapper, or a SQL connection. A Using block guarantees the disposal of one or more such resources when your code is finished with them. This makes them available for other code to use.
Managed resources are disposed of by the .NET Framework garbage collector (GC) without any extra coding on your part. You do not need a Using block for managed resources. However, you can still use a Using block to force the disposal of a managed resource instead of waiting for the garbage collector.
A Using block has three parts: acquisition, usage, and disposal.
�?� Acquisition means creating a variable and initializing it to refer to the system resource. The Using statement can acquire one or more resources, or you can acquire exactly one resource before entering the block and supply it to the Using statement. If you supply resourceexpression, you must acquire the resource before passing control to the Using statement.
�?� Usage means accessing the resources and performing actions with them. The statements between Using and End Using represent the usage of the resources.
�?� Disposal means calling the Dispose method on the object in resourcename. This allows the object to cleanly terminate its resources. The End Using statement disposes of the resources under the Using block's control.


Please feel free to comment with suggestions or corrections if I've missed something.  Otherwise, I hope this helps others sort out this important topic and is helpful to others.

D
0 Kudos
KirkKuykendall
Occasional Contributor III
Careful with FinalReleaseCOMObject though.  What might not be apparent is that if the same featureclass is opened in two different places, calling FinalReleaseComObject on one variable can cause an "object disconnected from its underlying com object" exception when the other variable is used.
ReleaseComObject just decrements by one, so no exception is thrown.
Code below prints "same object".

try
{
    IFeatureWorkspace fws = (IFeatureWorkspace)OpenWS(@"D:\Projects\SAWS\data\March02Test1.gdb");
    IFeatureClass fc1 = fws.OpenFeatureClass("SAWS_ADDRESSES");
    IFeatureClass fc2 = fws.OpenFeatureClass("SAWS_ADDRESSES");
    if (fc1 == fc2)
        Debug.Print("same object");
    else
        Debug.Print("different object");

    //Marshal.FinalReleaseComObject(fc1); 
    Marshal.ReleaseComObject(fc1);
    Debug.Print("{0} records", fc2.FeatureCount(null));
}
catch (Exception ex)
{
    Debug.Print(ex.Message);
}


Apparently ComReleaser calls FinalReleaseComObject when it disposes:
try
{
    IFeatureWorkspace fws = (IFeatureWorkspace)OpenWS(@"D:\Projects\SAWS\data\March02Test1.gdb");
    IFeatureClass fc1 = fws.OpenFeatureClass("SAWS_ADDRESSES");

    using (ComReleaser comReleaser = new ComReleaser())
    {
        IFeatureClass fc2 = fws.OpenFeatureClass("SAWS_ADDRESSES");
        if (fc1 == fc2)
            Debug.Print("same object");
        else
            Debug.Print("different object");
        comReleaser.ManageLifetime(fc2);
        Debug.Print("{0} records", fc2.FeatureCount(null));
    }
    // next line throws exception ...
    Debug.Print("{0} records", fc1.FeatureCount(null));
}
catch (Exception ex)
{
    Debug.Print(ex.Message);
}


This might be a hard bug to track down.  I've never seen a case where failure to releasecomobject on a featureclass causes problems, so maybe releasing is not worth the risk.
0 Kudos
DavidPlume1
New Contributor III
Thanks again Kirk,

Yes, it seems like, unlike the old days working in MFC C++, the best policy is to leave your objects alone; trying to explicitly clean them up can cause more harm than good.  This confirms the adage: no good deed goes unpunished!  

I found the following link that seems to confirm this and offers a bit of insight into how to think about COM objects and an approach to managing them:

Please see:

http://social.msdn.microsoft.com/Forums/en/vsto/thread/d11c67eb-3ad4-4e36-8707-37e671a37279

From that link:

Hi Gary,

Yes, managing Runtime Callable Wrappers (RCWs) can be tricky.  However, depending on what you are doing, you may not need to manage them at all.  As you know RCW's will hold a reference to their underlying COM object.  When the RCW's finalizer is run, they will release their reference. 

So when do RCW's get finalized?  At a minimum, they get finalized when the AppDomain is torn down.  During teardown, all objects are collected regardless of whether they are rooted.  Before AppDomain teardown, there are two other ways that RCWs could be finalized.  The first would be when the objects are no longer rooted and therefore become eligable for collection.  If the GC0 heap fills up, a garbage collection will occur, and those RCWs that are eligible will be finalized.  The second way finalization can happen is if you explicitly force a garbage collection by calling GC.Collect.  If you do that, any RCWs eligible for collection will be finalized.  By calling WaitForPendingFinalizers, you ensure that the finalizer thread has finalized all of the objects in the queue before your thread continues.

In addition, as you are aware, you can deterministically force the RCWs to release their reference by calling either Marshal.ReleaseComObject or Marshal.FinalReleaseComObject.  The difference between the two calls is this.  RCW's have a reference count of their own which gets bumped when the IUnknown is marshalled across AppDomain boundaries.  Calling Marshal.ReleaseComObject will only actually release when the RCW reference count goes to zero--otherwise it has the effect of decrementing this internal count.  Marshal.FinalReleaseComObject, however, will call the release regardless of what the RCW reference count is.

So the real question is when do you need to be explicit about enforcing RCW finalization or calling Marshal.Final/ReleaseComObject?  The answer is whenever you can't afford to wait for GC to happen (knowing that it might not occur until shutdown).  The two most likely reasons would be if the object is holding onto a resource (such as a file handle) or if memory pressure caused by keeping the object(s) alive was hurting performance.

If you know you are going to need to deterministically control the RCW cleanup, the best thing to do is to keep them isloated (and not pass them around) so that you can just call Marshal.FinalReleaseComObject when you are done with them.  As long as you can guarantee that you won't try to call into the RCW again after you make that call, then this is a safe approach.  This is better than trying to force a GC yourself since doing that will promote any existing objects in the heap to later generations which will mean they will potentially hang around in memory longer than they would have otherwise.

That said, the ideal is to do nothing and just let the system take care of everything.  Managing this stuff on your own is harder, so be sure you understand your reasons for doing so before you take that on.

Sincerely,

Geoff Darst
Microsoft VSTO Team


Regards
David
0 Kudos
nicogis
MVP Frequent Contributor
here there is a wrapper class for finalization com library

http://msdn.microsoft.com/en-us/magazine/cc163316.aspx
0 Kudos
FridjofSchmidt
Occasional Contributor
Apparently ComReleaser calls FinalReleaseComObject when it disposes [...]
This might be a hard bug to track down.  I've never seen a case where failure to releasecomobject on a featureclass causes problems, so maybe releasing is not worth the risk.


Kirk,

Apparently this is not a bug, although I thought so too. According to the documentation (http://resources.esri.com/help/9.3/ArcGISDesktop/dotnet/ESRI.ArcGIS.ADF/APIRef_01/ESRI.ArcGIS.ADF.Co...) this is the expected behavior:
"Marshal.ReleaseComObject will be called during the disposal process on this Interface pointer until its RCW reference count becomes 0"

I wonder why ESRI implemented it that way, because, as you said, when using the same feature class in two different places, and its lifetime is being managed in one place, it may fail in the other.
0 Kudos
USFS_AGOLAdministrator
New Contributor III
I am trying to use comReleaser.  What library do I have to reference to get it to show up?  When I use the following line:
using (ComReleaser comReleaser = new ComReleaser())
comReleaser is not recognized.  Searching help does not reveal the library to reference.  Can anyone help?  Thanks!
0 Kudos
SAULRAMOS_PEREDO1
New Contributor

I'm with the same problem... using ArcGIS 10.2.2  and visual studio 2012 ....

you solved it???

thanks!!!

0 Kudos
KenBuja
MVP Esteemed Contributor
It's in ESRI.ArcGIS.ADF.ComReleaser. In ArcGIS 10, you have to add the reference ESRI.ArcGIS.ADF.Connection.Local
0 Kudos