GOTCHA 38 Depending on Finalize() can tie up critical resources


GOTCHA #38 Depending on Finalize() can tie up critical resources

When an object becomes inaccessible it may remain in that state for an unpredictable amount of time. The garbage collector decides when the memory needs to be reclaimed based on its own heuristics, which include factors such as the amount of memory being used. The problem is that the time between when you have quit using the object and when Finalize() is called on it might be very long. If your object is using some critical unmanaged resources, you will be holding onto them in the meantime. This may lead to resource contention that is not detected or handled well within your .NET program.

Consider the case where a .NET object communicates with a COM component running in a different process. The .NET object is very small (say only a few bytes in size). The COM object it communicates with, however, is large. Now, say you have a loop where you create one .NET object per iteration, using it and letting go of the reference. When will the unmanaged resource be released?

The answer, based on what you've read so far in this chapter, is eventually-- but who knows exactly when?

Fortunately, .NET provides a mechanism to support synchronous cleanup of resources used by objects. If an object implements the IDisposable interface, it lets its users know that it offers a Dispose() method they can call to release the object. (Dispose() is the only method defined by IDisposable.) Because the users call Dispose() while the object is still accessible, it can free both managed and unmanaged resources.

An illustration of this is shown in Example 5-5. The code defines the Wrapper class, and states that it implements IDisposable. The Dispose() method cleans up the COM object by calling the ReleaseComObject() method of the System.Runtime.InteropServices.Marshal class. As a fail-safe mechanism to make sure the COM object gets released, the Finalize() method does the same thing. (ReleaseComObject() is discussed in Gotcha #65, "Release of COM object is confusing.")

Example 5-5. Using unmanaged resources from .NET

C# (ResourceHold)

 //Wrapper.cs using System; using ACOMCompLib; namespace FinalizePeril {     public class Wrapper : IDisposable     {         IMyComp comp = new MyCompClass();         public int doSomething()         {             int result;     comp.doSomething(out result);             return result;         }         ~Wrapper()         {     System.Runtime.InteropServices.Marshal.ReleaseComObject(comp);         }         #region IDisposable Members                   public void Dispose()         {     System.Runtime.InteropServices.Marshal.ReleaseComObject(comp);         }         #endregion     } } //Test.cs using System; using ACOMCompLib; namespace FinalizePeril {     class Test     {         [STAThread]         static void Main(string[] args)         {             int iterations = Convert.ToInt32(args[0]);             int result = 0;     for(int i = 0; i < iterations; i++)             {         Wrapper theWrapper = null;                 try                 {             theWrapper = new Wrapper();             result = theWrapper.doSomething();                 }                 finally                 {             theWrapper.Dispose();                 }             }             Console.WriteLine(result);             Console.WriteLine("End of Main");         }     } } 

VB.NET (ResourceHold)

 'Wrapper.vb Imports ACOMCompLib Public Class Wrapper     Implements IDisposable     Dim comp As IMyComp = New MyCompClass     Public Function doSomething() As Integer         Dim result As Integer         comp.doSomething(result)         Return result     End Function     Public Sub Dispose() Implements System.IDisposable.Dispose         System.Runtime.InteropServices.Marshal.ReleaseComObject(comp)     End Sub     Protected Overrides Sub Finalize()         MyBase.Finalize()         System.Runtime.InteropServices.Marshal.ReleaseComObject(comp)     End Sub End Class 'Test.vb Module Test     Sub Main(ByVal args() As String)         Dim iterations As Integer = Convert.ToInt32(args(0))         Dim result As Integer = 0         For i As Integer = 0 To iterations - 1             Dim theWrapper As Wrapper             Try         theWrapper = New Wrapper         result = theWrapper.doSomething()             Finally         theWrapper.Dispose()             End Try             Console.WriteLine(result)             Console.WriteLine("End of Main")         Next     End Sub End Module 

In the example, one instance of Wrapper is created for each pass through the loop. Each object of Wrapper has a reference to a COM component. What would happen if you didn't call theWrapper.Dispose()? Well, if the COM component is in-process, you don't have much to worry about. As the memory usage increases, the garbage collector will kick in and Finalize() the inaccessible objects. However, if the COM component is out-of-process, then you are simply out of luck, especially if the COM components are large. As far as the CLR is concerned it will have no reason to garbage-collect because the memory utilization on its side is pretty low. As a result, it will not call Finalize().

A good analogy is to consider having two small tables of the same size. On one table you place very small objects, say peanuts. For each peanut you place on the first table, you place a larger object on the second table, say a watermelon. The second table will fill up pretty quickly, while the first table has a lot of free space. This is exactly what happens in the example above if you neglect to call theWrapper.Dispose(). The Wrapper and the Runtime Callable Wrapper (RCW) that it uses are very small objects and thousands of these can be held in memory. However, if the related COM objects are watermelon-sized, you get into memory contention on the COM component server side. By calling theWrapper.Dispose(), you make sure the COM objects get released each time through the loop.

You can't predict exactly when Finalize() will be called on an object; thus, any resource not released by the programmer will be held until that happens. This could cause some undesirable side effects. By implementing the IDisposable interface and its Dispose() method, you give users of your object a way to clean it up synchronously.

As I pointed out earlier, the Finalize() and Dispose() methods of the Wrapper class both call ReleaseComObject(). Of course, you don't want to repeat the code in both. (Refer to the Don't Repeat Yourself (DRY) principle in [Hunt00].) You'll see how to refactor these in Gotcha #40, "Implementing IDisposable isn't enough."

IN A NUTSHELL

Do not rely on the CLR to call the Finalize() method. Allow users of your object to properly clean up by implementing the System.IDisposable interface. This way, they can call the Dispose() method on the object when they are done using it.

SEE ALSO

Gotcha #35, "Writing Finalize( ) is rarely a good idea," Gotcha #36, "Releasing managed resources in Finalize( ) can wreak havoc," Gotcha #39, "Using Finalize() on disposed objects is costly," Gotcha #40, "Implementing IDisposable isn't enough," Gotcha #41, "Using the Dispose Design Pattern doesn't guarantee cleanup," and Gotcha #65, "Release of COM object is confusing."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net