.NET Framework 1.1 Performance Guidelines - Finalize and Dispose

From Guidance Share

Jump to: navigation, search

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


Contents

Call Close or Dispose on Classes that Support It

If the managed class you use implements Close or Dispose, call one of these methods as soon as you are finished with the object. Do not simply let the resource fall out of scope. If an object implements Close or Dispose, it does so because it holds an expensive, shared, native resource that should be released as soon as possible.

!!!Disposable Resources Common disposable resources include the following:

  • Database-related classes: SqlConnection, SqlDataReader, and SqlTransaction.
  • File-based classes: FileStream and BinaryWriter.
  • Stream-based classes: StreamReader, TextReader, TextWriter, BinaryReader, and TextWriter.
  • Network-based classes: Socket, UdpClient, and TcpClient.

For a full list of classes that implement IDisposable in the .NET Framework, see "IDisposable Interface" in the .NET Framework Class Library on MSDN at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemIDisposableClassTopic.asp.

COM Objects

In server scenarios where you create and destroy COM objects on a per-request basis, you may need to call System.Runtime.InteropServices.Marshal.ReleaseComObject.

The Runtime Callable Wrapper (RCW) has a reference count that is incremented every time a COM interface pointer is mapped to it (this is not the same as the reference count of the IUnknown AddRef/Release methods). The ReleaseComObject method decrements the reference counts of the RCW. When the reference count reaches zero, the runtime releases all its references on the unmanaged COM object.

For example, if you create and destroy COM objects from an ASP.NET page, and you can track their lifetime explicitly, you should test calling ReleaseComObject to see if throughput improves.

Enterprise Services (COM+)

You are not recommended to share serviced components or COM or COM+ objects in cases where your objects are created in a nondefault context. An object can end up in a nondefault context either because your component is a serviced component configured in COM+ or because your component is a simple COM component that is placed in a nondefault context by virtue of its client. For example, clients such as ASP.NET pages running in a transaction or running in ASPCOMPAT mode are always located inside a COM+ context. If your client is a serviced component itself, the same rule applies.

The main reason for not sharing serviced components is that crossing a COM+ context boundary is expensive. This issue is increased if your client-side COM+ context has thread affinity because it is located inside an STA.

In such cases, you should follow acquire, work, release semantics. Activate your component, perform work with it, and then release it immediately. When you use Enterprise Services and classes that derive from System.EnterpriseServices.ServicedComponent, you need to call Dispose on those classes.

If the component you call into is an unmanaged COM+ component, you need to call Marshal.ReleaseComObject. In the case of nonconfigured COM components (components not installed in the COM+ catalog) if your client is inside a COM+ context and your COM component is not agile, it is still recommended that you call Marshal.ReleaseComObject.


Use the using Statement in C# and Try/Finally Blocks in Visual Basic .NET to Ensure Dispose Is Called

Call Close or Dispose inside a Finally block in Visual Basic .NET code to ensure that the method is called even when an exception occurs.

Dim myFile As StreamReader
myFile = New StreamReader("C:\\ReadMe.Txt")
Try 
  String contents = myFile.ReadToEnd()
  '... use the contents of the file
Finally 
  myFile.Close()
End Try

The using Statement in C#

For C# developers, the using statement automatically generates a try and finally block at compile time that calls Dispose on the object allocated inside the using block. The following code illustrates this syntax.

using( StreamReader myFile = new StreamReader("C:\\ReadMe.Txt")){
      string contents = myFile.ReadToEnd();
      //... use the contents of the file

} // dispose is called and the StreamReader's resources released

During compilation, the preceding code is converted into the following equivalent code.

StreamReader myFile = new StreamReader("C:\\ReadMe.Txt");
try{
  string contents = myFile.ReadToEnd();
  //... use the contents of the file
}
finally{
  myFile.Dispose();
}

Note The next release of Visual Basic .NET will contain the equivalent of a using statement.


Do Not Implement Finalize Unless Required

Implementing a finalizer on classes that do not require it adds load to the finalizer thread and the garbage collector. Avoid implementing a finalizer or destructor unless finalization is required.

Classes with finalizers require a minimum of two garbage collection cycles to be reclaimed. This prolongs the use of memory and can contribute to memory pressure. When the garbage collector encounters an unused object that requires finalization, it moves it to the "ready-to-be-finalized" list. Cleanup of the object's memory is deferred until after the single specialized finalizer thread can execute the registered finalizer method on the object. After the finalizer runs, the object is removed from the queue and literally dies a second death. At that point, it is collected along with any other objects. If your class does not require finalization, do not implement a Finalize method.


Implement Finalize Only If You Hold Unmanaged Resources across Client Calls

Use a finalizer only on objects that hold unmanaged resources across client calls. For example, if your object has only one method named GetData that opens a connection, fetches data from an unmanaged resource, closes the connection, and returns data, there is no need to implement a finalizer. However, if your object also exposes an Open method in which a connection to an unmanaged resource is made, and then data is fetched using a separate GetData method, it is possible for the connection to be maintained to the unmanaged resource across calls. In this case, you should provide a Finalize method to clean up the connection to the unmanaged resource, and in addition use the Dispose pattern to give the client the ability to explicitly release the resource after it is finished.

Note You must be holding the unmanaged resource directly. If you use a managed wrapper you do not need your own finalizer, although you might still choose to implement IDisposable so that you can pass along the dispose request to the underlying object.


Move the Finalization Burden to the Leaves of Object Graphs

If you have an object graph with an object referencing other objects (leaves) that hold unmanaged resources, you should implement the finalizers in the leaf objects instead of in the root object.

There are several reasons for this. First, the object that is being finalized will survive the first collection and be placed on the finalization list. The fact that the object survives means that it could be promoted to an older generation just like any other object, increasing the cost of collecting it in the future. Second, because the object survived, any objects it might be holding will also survive, together with their sub objects, and so on. So the entire object graph below the finalized object ends up living longer than necessary and being collected in a more expensive generation.

Avoid both these problems by making sure that your finalizable objects are always leaves in the object graph. It is recommended that they hold the unmanaged resource they wrap and nothing else.

Moving the finalization burden to leaf objects results in the promotion of only the relevant ones to the finalization queue, which helps optimize the finalization process.


If You Implement Finalize, Implement IDisposable

You should implement IDisposable if you implement a finalizer. In this way, the calling code has an explicit way to free resources by calling the Dispose method.

You should still implement a finalizer along with Dispose because you cannot assume that the calling code always calls Dispose. Although costly, the finalizer implementation ensures that resources are released.


If You Implement Finalize and Dispose, Use the Dispose Pattern If you implement Finalize and Dispose, use the Dispose pattern

The Dispose pattern defines the way you should implement dispose (and finalizer) functionality on all managed classes that maintain resources that the caller must be allowed to explicitly release. To implement the Dispose pattern, do the following:

  • Create a class that derives from IDisposable.
  • Add a private member variable to track whether IDisposable.Dispose has already been called. Clients should be allowed to call the method multiple times without generating an exception. If another method on the class is called after a call to Dispose, you should throw an ObjectDisposedException.
  • Implement a protected virtual void override of the Dispose method that accepts a single bool parameter. This method contains common cleanup code that is called either when the client explicitly calls IDisposable.Dispose or when the finalizer runs. The bool parameter is used to indicate whether the cleanup is being performed as a result of a client call to IDisposable.Dispose or as a result of finalization.
  • Implement the IDisposable.Dispose method that accepts no parameters. This method is called by clients to explicitly force the release of resources. Check whether Dispose has been called before; if it has not been called, call Dispose(true) and then prevent finalization by calling GC.SuppressFinalize(this). Finalization is no longer needed because the client has explicitly forced a release of resources.
  • Create a finalizer, by using destructor syntax. In the finalizer, call Dispose(false).

C# Example of Dispose

Your code should look like the following.

public sealed class MyClass: IDisposable
{
  // Variable to track if Dispose has been called
  private bool disposed = false;
  // Implement the IDisposable.Dispose() method
  public void Dispose(){
    // Check if Dispose has already been called 
    if (!disposed)
    {
      // Call the overridden Dispose method that contains common cleanup code
      // Pass true to indicate that it is called from Dispose
      Dispose(true);
      // Prevent subsequent finalization of this object. This is not needed 
      // because managed and unmanaged resources have been explicitly released
      GC.SuppressFinalize(this);
    }
  }
  // Implement a finalizer by using destructor style syntax
  ~MyClass() {
    // Call the overridden Dispose method that contains common cleanup code
    // Pass false to indicate the it is not called from Dispose
    Dispose(false);
  }
  // Implement the override Dispose method that will contain common
  // cleanup functionality
  protected virtual void Dispose(bool disposing){
   if(disposing){
     // Dispose time code
     . . .
   }
   // Finalize time code
   . . .
  }
 …}

Passing true to the protected Dispose method ensures that dispose specific code is called. Passing false skips the Dispose specific code. The Dispose(bool) method can be called directly by your class or indirectly by the client.

If you reference any static variables or methods in your finalize-time Dispose code, make sure you check the Environment.HasShutdownStarted property. If your object is thread safe, be sure to take whatever locks are necessary for cleanup.

Use the HasShutdownStarted property in an object's Dispose method to determine whether the CLR is shutting down or the application domain is unloading. If that is the case, you cannot reliably access any object that has a finalization method and is referenced by a static field.

protected virtual void Dispose(bool disposing){
  if(disposing){
    // dispose-time code
  . . .
  }
  // finalize-time code
  CloseHandle();
  if(!Environment.HasShutDownStarted) 
  { //Debug.Write or Trace.Write – static methods
    Debug.WriteLine("Finalizer Called");
  }
  disposed = true;
}

Visual Basic .NET Example of Dispose

The Visual Basic .NET version of the Dispose pattern is shown in the following code sample.

'Visual Basic .NET Code snippet
Public Class MyDispose Implements IDisposable
   
   Public Overloads Sub Dispose() Implements IDisposable.Dispose
       Dispose(True)
       GC.SuppressFinalize(Me) ' No need call finalizer
   End Sub

   Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
       If disposing Then
           ' Free managed resources
       End If
       ' Free unmanaged resources
   End Sub

   Protected Overrides Sub Finalize()
       Dispose(False)
   End Sub
End Class


Suppress Finalization in Your Dispose Method

The purpose of providing a Dispose method is to allow the calling code to release unmanaged resources as soon as possible and to prevent two cycles being taken for the object's cleanup. If the calling code calls Dispose, you do not want the garbage collector to call a finalizer because the unmanaged resources will have already been returned to the operating system. You must prevent the garbage collector from calling the finalizer by using GC.SuppressFinalization in your Dispose method.

public void Dispose()
{
  // Using the dispose pattern
  Dispose(true); 
  // ... release unmanaged resources here
  GC.SuppressFinalize(this);
}


Allow Dispose to Be Called Multiple Times

Calling code should be able to safely call Dispose multiple times without causing exceptions. After the first call, subsequent calls should do nothing and not throw an ObjectDisposedException for subsequent calls.

You should throw an ObjectDisposedException exception from any other method (other than Dispose) on the class that is called after Dispose has been called.

A common practice is to keep a private variable that denotes whether Dispose has been called.

public class Customer : IDisposable{
  private bool disposed = false;
  . . .
  public void SomeMethod(){
    if(disposed){
      throw new ObjectDisposedException(this.ToString());
          }
          . . .
    } 
  public void Dispose(){
    //check before calling your Dispose pattern
    if (!disposed)
    { ... }
  }
  . . .
}


Call Dispose On Base Classes and On IDisposable Members

If your class inherits from a disposable class, then make sure that it calls the base class's Dispose. Also, if you have any member variables that implement IDisposable, call Dispose on them, too.

The following code fragment demonstrates calling Dispose on base classes.

public class BusinessBase : IDisposable{
  public void Dispose() {...}
  protected virtual void Dispose(bool disposing)  {}
  ~BusinessBase() {...}
}
public class Customer : BusinessBase, IDisposable{
private bool disposed = false;
 protected virtual void Dispose(bool disposing) {
   // Check before calling your Dispose pattern
   if (!disposed){
     if (disposing) {
       // free managed objects
     }
     // free unmanaged objects
     base.Dispose(disposing);
     disposed = true;
   }
 }


Keep Finalizer Code Simple to Prevent Blocking

Finalizer code should be simple and minimal. The finalization happens on a dedicated, single finalizer thread. Apply the following guidelines to your finalizer code:

  • Do not issue calls that could block the calling thread. If the finalizer does block, resources are not freed and the application leaks memory.
  • Do not use thread local storage or any other technique that requires thread affinity because the finalizer method is called by a dedicated thread, separate from your application's main thread.

If multiple threads allocate many finalizable objects, they could allocate more finalizable objects in a specific timeframe than the finalizer thread can clean up. For this reason, Microsoft may choose to implement multiple finalizer threads in a future version of the CLR. As a result, it is recommended that you write your finalizers so they do not depend on shared state. If they do, you should use locks to prevent concurrent access by other instances of the same finalizer method on different object instances. However, you should try to keep finalizer code simple (for example, nothing more complicated than just a CloseHandle call) to avoid these issues.


Provide Thread Safe Cleanup Code Only if Your Type Is Thread Safe

If your type is thread safe, make sure your cleanup code is also thread safe. For example, if your thread safe type provides both Close and Dispose methods to clean up resources, ensure you synchronize threads calling Close and Dispose simultaneously.

Personal tools