Interop (.NET 1.1) Performance Guidelines - Marshal.ReleaseComObject

From Guidance Share
Jump to navigationJump to search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman


Consider Calling ReleaseComObject in Server Applications

When you reference a COM object, you actually maintain a reference to an RCW. The RCW holds an internal pointer to the COM object's IUnknown interface. During finalization of the RCW, the CLR finalizer thread calls the RCW's finalizer, which in turn calls IUnknown::Release to decrement the COM object's reference count. When the reference count reaches zero, the COM object is released from the memory.

.NET memory management is nondeterministic, which can cause problems when you need to deterministically release COM objects in server applications, such as ASP.NET applications. You can use Marshal.ReleaseComObject to help solve this problem.

Note You should only call ReleaseComObject when you absolutely have to.


How ReleaseComObject Works

An RCW maintains an internal marshaling count, which is completely separate from the COM object reference count. When you call ReleaseComObject, the RCW's internal marshaling count is decremented. When the internal marshaling count reaches zero, the RCW's single reference count on the underlying COM object is decremented. At this point, the unmanaged COM object is released and its memory is freed, and the RCW becomes eligible for garbage collection.

The CLR creates exactly one RCW for each COM object. The RCW maintains a single reference count on its associated COM object, irrespective of how many interfaces from that COM object have been marshaled into the managed process in which the RCW is located. Figure 7.4 shows the relationship between the RCW, its clients in a managed process, and the associated COM object.


Figure 7.4: RCW's relationship to managed code and an unmanaged COM object

If multiple interface pointers have been marshaled, or if the same interface has been marshaled multiple times by multiple threads, the internal marshaling count in the RCW will be greater than one. In this situation, you need to call ReleaseComObject in a loop.


When to Call ReleaseComObject

Client code that uses a managed object that exposes a Dispose method should call the Dispose method as soon as it is finished with the object to ensure that resources are released as quickly as possible. Knowing when to call ReleaseComObject is trickier. You should call ReleaseComObject when:

You create and destroy COM objects under load from managed code. If there is sufficient load on your application to necessitate quick COM object disposal and recovery of resources, consider ReleaseComObject. This is generally the case for server workloads. For example, you may need to call ReleaseComObject if your ASP.NET page creates and destroys COM objects on a per-request basis. Your ASP.NET code calls a serviced component that wraps and internally calls a COM component. In this case, you should implement Dispose in your serviced component and your Dispose method should call ReleaseComObject. The ASP.NET code should call your serviced component's Dispose method. Your COM component relies on an eager release of its interface pointers to IUnknown. One approach is to assume that eager release is unnecessary. Then, if you find that you have scaling problems because a specific COM component must be eagerly released, come back and add the ReleaseComObject for it. In general, if you are calling COM from managed code under load (for example, in server scenarios), you need to consider ReleaseComObject.


When Not to Call ReleaseComObject

You should not call ReleaseComObject in the following circumstances:

  • If you use the COM object across client calls, do not call ReleaseComObject unless you are completely done. An exception is generated if you try to access an object that is already released.
  • If you use the COM object from multiple threads (such as when you cache or pool the object), do not call ReleaseComObject until you are completely done. An exception is generated if you try to access an object that is released.

If you do not call ReleaseComObject, the RCWs are cleaned up in one of two ways:

  • When a garbage collection happens, the finalizer thread releases RCWs that are not in use.
  • When a COM object is activated or when an interface pointer enters the runtime for the first time. If this occurs on an MTA thread the runtime will clean up all the RCWs no longer in use in the current context. If this occurs on an STA thread the runtime will clean up all the RCWs no longer in use in all contexts in that STA apartment


How to Call ReleaseComObject

When you call ReleaseComObject, follow these guidelines:

  • Evaluate whether you need a loop to release all interfaces. In most cases, you can simply call ReleaseComObject once. For example, in cases where you acquire a COM object interface pointer, work with it, and then release it, you should not implement a loop. This usage pattern is typical in server applications.

In cases where you have a marshaling count greater than one, you need to use a loop. This is the case when the marshaling count is incremented every time the pointer to IUnknown is marshaled into managed code from unmanaged code and ends up with the same RCW. Therefore you need to call ReleaseComObject in a loop until the returned marshaling count equals zero.

For example, if you call an unmanaged method ten times in a loop on the same thread, and the method returns the same object ten times, the underlying wrapper will have a marshaling count of ten. In this case, you must call ReleaseComObject ten times in a loop. This can occur in cases where you use ActiveX controls, where your code might query a contained property multiple times.

A simple approach is to call ReleaseComObject in a loop until its return value (the unmanaged reference count) reaches zero as shown below.


  while(Marshal.ReleaseComObject(yourComObject)!=0);

If any thread subsequently attempts to access the released COM object through the RCW, a NullReferenceException exception is generated.

Note At the time of this writing, the .NET Framework 2.0 (code-named "Whidbey") provides a method named FinalReleaseComObject that will bypass the marshaling count logic. This means that you will not need to use a loop to repeatedly call ReleaseComObject.

  • Use a finally block. It is good practice to place calls to ReleaseComObject in a finally block as shown in the following example to ensure that it is called, even in the event of an exception.


  // Create the COM object
  Account act = new Account();
  try
  {
    // Post money into the account
    act.Post(5, 100);
  }
  finally
  {
    // Make sure that the underlying COM object is immediately freed
    System.Runtime.InteropServices.Marshal.ReleaseComObject(act);
  }


  • Setting objects to null or Nothing. It is common practice for Visual Basic 6 developers to set an object reference to Nothing as follows.


  Set comObject = Nothing

If you would set a reference to null or Nothing to make a graph of objects unreachable in a pure managed scenario, you would use the same technique with graphs that contain managed objects and/or references to unmanaged objects.


Do Not Force Garbage Collections with GC.Collect

A common approach for releasing unmanaged objects is to set the RCW reference to null, and call System.GC.Collect followed by System.GC.WaitForPendingFinalizers. This is not recommended for performance reasons, because in many situations it can trigger the garbage collector to run too often. Code written by using this approach significantly compromises the performance and scalability of server applications. You should let the garbage collector determine the appropriate time to perform a collection.


Do Not Force Garbage Collections with GC.Collect

A common approach for releasing unmanaged objects is to set the RCW reference to null, and call System.GC.Collect followed by System.GC.WaitForPendingFinalizers. This is not recommended for performance reasons, because in many situations it can trigger the garbage collector to run too often. Code written by using this approach significantly compromises the performance and scalability of server applications. You should let the garbage collector determine the appropriate time to perform a collection.