20 Replies Latest reply on Nov 6, 2014 7:03 AM by elreysaul007

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

    david.plume
      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
        • Re: What's Up with ComReleaser? Is it really working?
          Neil
          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.
          • Re: What's Up with ComReleaser? Is it really working?
            mackayj80
            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
            • Re: What's Up with ComReleaser? Is it really working?
              kirkktx
              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!
              • Re: What's Up with ComReleaser? Is it really working?
                mackayj80
                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
                • Re: What's Up with ComReleaser? Is it really working?
                  kirkktx
                  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,
                  • Re: What's Up with ComReleaser? Is it really working?
                    mackayj80
                    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
                    • Re: What's Up with ComReleaser? Is it really working?
                      david.plume
                      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
                      • Re: What's Up with ComReleaser? Is it really working?
                        mackayj80
                        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
                        • Re: What's Up with ComReleaser? Is it really working?
                          Neil
                          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.
                          • Re: What's Up with ComReleaser? Is it really working?
                            kirkktx
                            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

                            • Re: What's Up with ComReleaser? Is it really working?
                              david.plume
                              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.
                              • Re: What's Up with ComReleaser? Is it really working?
                                david.plume
                                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
                                • Re: What's Up with ComReleaser? Is it really working?
                                  kirkktx
                                  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.
                                  • Re: What's Up with ComReleaser? Is it really working?
                                    david.plume
                                    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
                                    • Re: What's Up with ComReleaser? Is it really working?
                                      ciava.at
                                      here there is a wrapper class for finalization com library

                                      http://msdn.microsoft.com/en-us/magazine/cc163316.aspx
                                      • Re: What's Up with ComReleaser? Is it really working?
                                        fridjof
                                        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.ComReleaser~ManageLifetime.html) 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.
                                        • Re: What's Up with ComReleaser? Is it really working?
                                          USForestServiceAdmin
                                          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!
                                          • Re: What's Up with ComReleaser? Is it really working?
                                            kenbuja
                                            It's in ESRI.ArcGIS.ADF.ComReleaser. In ArcGIS 10, you have to add the reference ESRI.ArcGIS.ADF.Connection.Local