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

9932
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
NeilClemmons
Regular Contributor III
I don't think you should be setting the variables to Nothing before you exit the Using block.  From what I understand, the ComReleaser class is handling the calls to ReleaseComObject that are necessary to release any resources being used by the object instances.  You can't call ReleaseComObject on an object that has been set to Nothing so I imagine you are preventing the ComReleaser class from doing what it needs to do.  I personally haven't used the ComReleaser class myself so I'm not sure how well it works.  I prefer to release the objects myself by calling FinalReleaseComObject on them as needed.
0 Kudos
JamesMacKay
New Contributor
Dim pFeatCursor As ESRI.ArcGIS.Geodatabase.IFeatureCursor = Nothing
pComReleaser.ManageLifetime(pFeatCursor)


You have to provide the ComReleaser with references to the objects you want to manage. The approach above is basically "managing" a null reference. You need to move your ManageLifetime calls to a place like this:

Dim featCursor as IFeatureCursor =featureClass.Search(...)
comReleaser.ManageLifetime(featCursor)

Likewise, since you're using a non-recycling cursor, every row retrieved from the cursor is a new object, which means every row should be managed as well.

One thing to avoid is managing a RCW that you plan on using outside the scope of the using block. This error - "COM object that has been separated from its underlying RCW cannot be used." - indicates that's happening. Keep in mind that when you perform an explicit cast, for example from IFeatureClass to ITable, you're still referencing the same RCW.

Cheers,
James
0 Kudos
KirkKuykendall
Occasional Contributor III
Likewise, since you're using a non-recycling cursor, every row retrieved from the cursor is a new object, which means every row should be managed as well.


I've never noticed any ill side effects in apps (or extensions) that do not call ReleaseCOMObject on IFeature/IRow references - has anyone else?

For example if I have methods that return generic lists of IFeatures  (List<IFeature>) and releases the cursor when its done, do I really need to call ReleaseCOMObject on each feature when they are no longer in use?

Can anyone show me code where failing to call ReleaseCOMObject on an IFeature/IRow causes an exception?

Thanks!
0 Kudos
JamesMacKay
New Contributor
Kirk,

It definitely isn't as big of a deal as managing cursors; I've never seen a case where it throws an exception. It can help performance though, and ensures that things behave as expected in some workflows. One that comes to mind is trying to delete local geodatabase after having used a non-recycling cursor. Granted, that's not a common workflow, but it's one that does happen on occasion.

If a cursor is recycling, typically I use this kind of pattern:

using (ComReleaser comReleaser = new ComReleaser())
{
  ICursor cursor = table.Search(null, true);
  comReleaser.ManageLifetime(cursor);
  IRow row = null;
  Boolean isManaged = false;
  while ((row = cursor.NextRow()) != null)
  {
    if (!isManaged)
    {
      comReleaser.ManageLifetime(row);
      isManaged = true;
    }

    // Use the row...
  }
}


Whereas for non-recycling cursors I generally do something like this:

using (ComReleaser comReleaser = new ComReleaser())
{
  ICursor cursor = table.Search(null, false);
  comReleaser.ManageLifetime(cursor);
  IRow row = null;
  while ((row = cursor.NextRow()) != null)
  {
    try
    {
      // Use the row...
    }
    catch (Exception exc)
    {
      // Handle the exception...
    }
    finally
    {
      Marshal.ReleaseComObject(row);
    }
  }
}


... with the exception being the odd case where I have to use references to rows other than the last one fetched (like the List<IFeature> scenario you mentioned). In those cases I manage the rows using the ComReleaser.

(In the non-recycling case I've also used a nested ComReleaser at times...)

Cheers,
James
0 Kudos
KirkKuykendall
Occasional Contributor III
Hey James, thanks for the quick response.

Would you see any problems in having an architecture where a Data Access Layer (DAL) that returns a BindingList of Business Objects (BO's) such that:

  • the BO maintains a strong reference to a IObject (passed as constructor arg)

  • each field in IObject.Fields maps to a BO getter/setter that calls IObject.get_Value/set_Value.

  • BO implements IDisposable, and calls ReleaseCOMObject on the contained IRow in Dispose()

  • DAL calls ReleaseCOMObject on ICursor after returning BindingList<BO>.


Thanks again,
0 Kudos
JamesMacKay
New Contributor
Kirk,

I've done similar things in the past to allow attribute editing through the .NET PropertyGrid and it generally worked okay as long as all the IDisposable-ish plumbing's there, which it sounds like you've got. You might run into trouble for strange cases like the one I mentioned above - deleting a local geodatabase while rows remain unreleased - but I haven't seen too many other problems. One thing that comes to mind is if you allow the creation of rows through your BOs and you use a feature buffer under a hood you'll want to dispose of that feature buffer as well.

Cheers,
James
0 Kudos
DavidPlume1
New Contributor III
Neil, James and Kirk,

Your replies have been very helpful, thank you --  A tip of my hat to the masters!

For the sake of my own understanding and the thread then, regarding RCWs I am left wondering:

see:
http://edndoc.esri.com/arcobjects/9.2/NET/fe9f7423-2100-4c70-8bd6-f4f16d5ce8c0.htm
and
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx

Reading at MSDN,  I see that "Every time a COM interface pointer enters the common language runtime (CLR), it is wrapped in an RCW."  I take this to mean, essentially, that all our our ArcObjects in .NET have a corresponding RCW. 

Furthermore MSDN says: Therefore, use the ReleaseComObject only if it is absolutely required. If you want to call this method to ensure that a COM component is released at a determined time, consider using the FinalReleaseComObject method instead. FinalReleaseComObject will release the underlying COM component regardless of how many times it has re-entered the CLR. The internal reference count of the RCW is incremented by one every time the COM component re-enters the CLR. Therefore, you could call the ReleaseComObject method in a loop until the value returned is zero. This achieves the same result as the FinalReleaseComObject method.

So: it seems that a good rule of thumb would be -- if you explicitly  know that you should release the object, such as a cursor, then do it otherwise leave things to the garbage collector.

So consequently I'm wondering, is there a rule of thumb to help one detect which objects we should consider for explicit release, either by ReleaseComObject, FinalReleaseComObject or alternatively, ComReleaser?

Best Regards
David
0 Kudos
JamesMacKay
New Contributor
David,

Unfortunately it isn't a great rule of thumb since you have to be familiar with the implementation of the COM classes you're using (or go through trial and error), but if an object ties up resources or locks, I manage/release it. Also, when I'm in doubt about an object I release it... I'm not really sure why MSDN recommends leaving objects to the garbage collector, other than it keeps your code from getting cluttered up with potentially unnecessary release lines, or that prematurely releasing an object could leave you with a trashed RCW.

Within the ArcObjects Geodatabase library, the types I would suggest managing/releasing are:

  • Cursors

  • Datasets, i.e. tables, feature classes, feature datasets, rel. classes (but excluding name objects)

  • Workspaces (inc. versions, if you're connecting to multiple versions of an enterprise GDB at once)

  • Rows, features and attributed relationships

  • Row buffers and feature buffers

It's important to be aware of cases where "unique instancing" occurs, however. For example, if you connect to an ArcSDE workspace twice - once with a set of connection properties and once with an SDE connection file - assuming the username/password and version are the same, you'll be handed a single COM object and your .NET references will share a single RCW. In these kinds of situations you'll want to use ReleaseComObject rather than FinalReleaseComObject or the ComReleaser (although in a lot of the code I write I use a ComReleaser with workspaces, since I'm often only working with one workspace and don't have to worry about this). Other cases you have to look out for are:

  • Rows/features in an edit session - Being in an active geodatabase edit session guarantees that any time you retrieve a feature, whether through GetFeature or a cursor, only one instance of that feature will be created. If you call IFeature.GetFeature(1) and pull a feature from a cursor that had a where clause of "OBJECTID = 1", you'll get the same feature COM object and the same RCW.

  • Datasets - Dataset references are pooled in their workspace. If you try to open a feature class that was previously opened, you'll be handed a reference to the existing instance. Note that this is not true across different versions of an enterprise GDB.

Cheers,
James
0 Kudos
NeilClemmons
Regular Contributor III
I've never noticed any ill side effects in apps (or extensions) that do not call ReleaseCOMObject on IFeature/IRow references - has anyone else?

For example if I have methods that return generic lists of IFeatures  (List<IFeature>) and releases the cursor when its done, do I really need to call ReleaseCOMObject on each feature when they are no longer in use?

Can anyone show me code where failing to call ReleaseCOMObject on an IFeature/IRow causes an exception?

Thanks!


Hi Kirk,
I've never seen any exceptions thrown but in one of our applications a temporary geodatabase was being created, used, then deleted.  The code that deleted the geodatabase failed any time the user tried to repeat the same workflow without first closing the dialog and re-opening it (which we didn't want them to have to do).  I went over all of the code with a fine toothed comb several times releasing every object I could find to no avail.  I didn't bother with feature and row objects inside of loops because I was releasing the objects outside of the loops.  The last thing I tried was releasing the feature and row objects inside the loop because I had seen an ESRI example where it was being done and all of a sudden the geodatabase could be deleted without any problems.  Under normal circumstances I don't think you'll run into any problems if you don't release the objects but in specific cases like mine it's necessary. Hope this helps.
0 Kudos