IDisposable.Dispose


Finalizers

Resources of an object are cleaned up in a Finalize method. Before an object is removed from memory, the Finalize method is called to release collateral resources. As a group, Finalize methods are called finalizers. Every object inherits the Finalize method from the System.Object type. This is the syntax of the Finalize method:

  • Object.Finalize syntax:

    • protected virtual void Finalize()

Finalizers are not called programmatically; they are called automatically during the garbage collection cycle. Garbage collection is nondeterministic, which means that the finalizer may not be called immediately. This means that unmanaged resources, such as files, database connections, and devices, might not be released in a timely manner. This delay in relinquishing resources can have adverse side effects on this and possibly other applications.

Instead of a Finalize method, C# has a destructor. Coding a Finalize method explicitly causes a compiler error. Destructor methods are preceded with a tilde and share the name of the class.

Destructor syntax:

  • ~Classname()

The destructor syntax has the following limitations:

  • Destructors do not have modifiers.

  • Destructors are protected and virtual by default.

  • Destructors have no parameters.

  • Destructors cannot be overloaded.

  • Destructors do not have a return type. The implied return type of a destructor is void.

The C# compiler converts destructors to Finalize methods. Here is a simple destructor:

 ~ZClass() {     --propCount; } 

The C# compiler emits a Finalize method for the ZClass. The Finalize method supplants the destructor in Microsoft intermediate language (MSIL) code. Figure 14-5 shows the ZClass. Notice the Finalize method and the absence of the Destructor method.

image from book
Figure 14-5: View of the ZClass

The C# compiler generated the following MSIL code for the ZClass destructor (for clarity, extraneous code has been removed from the listing):

 .method family hidebysig virtual instance void         Finalize() cil managed {   .maxstack 2   .try   {     IL_0001:  ldsfld     uint8 Donis.CSharpBook.ZClass::propCount     IL_0006:  ldc.i4.1     IL_0007:  sub     IL_0008:  conv.u1     IL_0009:  stsfld      uint8 Donis.CSharpBook.ZClass::propCount     IL_000f:  leave.s     IL_0019   }   finally   {     IL_0011:  ldarg.0     IL_0012:  call        instance void [mscorlib]System.Object::Finalize()     IL_0018:  endfinally   }   IL_0019:  nop   IL_001a:  ret } 

The Finalize method provided by the C# compiler contains a termination handler. The try block contains the code of the destructor. In this circumstance, the count is decremented. The termination handler calls the Finalize method of the base class. Because the call is in a termination handler, the finalizer of the base class is called even if an unhandled exception is raised in the destructor. This means that calls to the Finalize method propagate through the class hierarchy even when an exception is raised. This also means that developers should never attempt to directly call the destructor of the base class. The manifested Finalize method automatically assumes this responsibility.

The following code defines a class hierarchy, in which each class in the hierarchy has a destructor. This demonstrates that the destructors are called bottom-up automatically. The destructor in the XClass throws an unhandled exception. Despite the error condition, the base destructors are called anyway. This confirms that the Finalize method of the base class is called in a termination handler:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             XClass obj=new XClass();         }     }     public class ZClass{         ~ZClass() {             Console.WriteLine("ZClass d'tor");         }     }     public class YClass: ZClass{         ~YClass() {             Console.WriteLine("YClass d'tor");         }     }     public class XClass: YClass{         ~XClass() {             Console.WriteLine("XClass d'tor");             throw new Exception("D'tor exception");         }     } } 

Many have lamented the decision to associate finalizers with destructors. C++ developers are familiar with destructors. They are an integral part of the C++ language. This familiarity creates a level of expectation, which is only partially honored in C#. There are credible differences between C++ and C# destructors, including the following:

  • C++ destructors can be called deterministically, whereas C# destructors are called nondeterministically.

  • C++ destructors can be virtual or nonvirtual, whereas C# destructors are implicitly virtual.

  • C++ destructors execute on the same thread that created the object, whereas C# destructors execute on a dedicated finalizer thread.

  • C++ destructors cannot be suppressed at run time, whereas C# destructors can be suppressed.

For the remainder of this chapter, C# destructors are referred to as finalizers to distinguish between C# and C++ destructors.

As mentioned, finalizers are called nondeterministically. In code, you cannot call them directly. When are finalizers called?

  • During the garbage collection process

  • When the AppDomain of an object is unloaded

  • When the GC.Collect method is called

  • When the CLR is shutting down

Knowing the specific reason for finalization can be helpful. For example, if finalization is part of normal garbage collection, you might choose to recycle the object. However, if the application domain is being unloaded, recycling a dead object would be pointless. You can confirm that finalization is initiated by an application unload or a CLR shutdown. The AppDomain .IsFinalizingForUnload method, which is an instance method, returns true if the application domain is unloading and finalization has begun on objects in that domain. Otherwise, the method returns false. The Environment.HasShutdownStarted property is a static property that returns true if the CLR is shutting down or the application domain is unloading. Otherwise, the property returns false.

The Recycle application recycles an instance of the ZClass type. When the object is being finalized, the Environment.HasShutdownStarted property is checked. If false, the reference to the object is re-established, which ends finalization. This resurrects the object (more about resurrection later). If the property is true, the object is not recycled. There is no reason to recycle an object if the current application domain is unloading or the CLR is shutting down. This is the ZClass type from the Recycle application:

 public class ZClass {     public ZClass()     {         ++propCount;         _count = propCount;     }     ~ZClass()     {         ++propCount;         ++_count;         AppDomain current = AppDomain.CurrentDomain;         if (!Environment.HasShutdownStarted)         {             Form1.obj = this;             GC.ReRegisterForFinalize(this);         }     }     private static int propCount = 0;     private int _count;     public int Count     {         get         {             return _count;         }     } } 

At application shutdown, finalizers have a limited amount of time to complete their tasks. This prevents an indefinite shutdown. Each Finalize method has two seconds to complete. If the time allotment is exceeded, that finalizer is terminated. The remaining finalizers then continue to execute. As a group, the Finalize methods have 40 seconds to complete all finalization chores. When this time limit is exceeded, the remaining finalizers are skipped. Forty seconds is nearly an eternity in processing time and should be sufficient to complete even the most extensive housecleaning of an application.

In the following application, you can adjust the duration of the finalizer and view the results. A number between 1 and 10 provides the best comparative results. Specify the value as a command-line argument:

 using System; using System.Threading; namespace Donis.CSharpBook{     public class Starter{         public static void Main(string [] args){             Shutdown.ZTime=int.Parse(args[0]);             ZClass [] obj=new ZClass[500];             for(int count=0;count<500;++count) {                 obj[count]=new ZClass();             }         }     }     public class Shutdown {         static public int ZTime=0;     }     public class ZClass{         public ZClass() {             ++globalcount;             localcount=globalcount;         }         private int localcount=0;         private static int globalcount=0;         ~ZClass() {             for(int i=0; i< Shutdown.ZTime; ++i) {                 Thread.Sleep(50);             }             Console.WriteLine(localcount+" "+                 "ZClass d'tor");         }     } } 

Finalizer Thread

The finalizer thread calls finalizers on objects waiting in the FReachable queue. After the finalizer is called, the object is removed from the FReachable queue and deleted from memory during the next garbage collection cycle. The finalizer thread executes the finalizer asynchronously. There is one finalizer thread that services all finalizers of the application, which creates a potential bottleneck. For this reason, finalizers should be short and simple. A long finalizer can delay the finalization of the remaining objects on the FReachable queue. In addition, this extends the lifetime of objects pending finalization and increases memory pressure on the managed heap. This causes more garbage collection, which is always expensive.

Finalizer Considerations

When implementing finalizers, there are several considerations. These considerations should help developers implement finalizers correctly. Some of these considerations emphasize that finalizers should be avoided whenever possible. For example, you should never implement an empty finalizer. In the C++ language, an empty destructor is harmless. In C#, an empty destructor (finalizer) is costly, as explained in this section.

Finalizers are expensive As mentioned, finalizers are expensive. At least two garbage collection cycles are required to collect a finalizable object that is not rooted. During the first garbage collection, the finalizable object is moved from the Finalizable queue to the FReachable queue. After the finalizer has been called, the memory of the object is reclaimed in a future garbage collection. The lifetime of objects referenced by the finalizable object are equally extended. They must wait for the finalizable object to be reclaimed before being released themselves. Additional object retention increases memory pressure, which causes additional garbage collection. Needless garbage collection is particularly expensive. The finalizable objects are promoted to later generations, which make a full garbage collection more likely. Full garbage collection is much more expensive than collecting only Generation 0. Actually, the extra expense of defining a finalizable object starts at its allocation. When a finalizable object is created, a reference to the object must be added to the Finalizable queue. Objects without finalizers avoid this additional expense.

Finalizers are not guaranteed Finalizers are not always called. Some of the reasons why a finalizer might not be called have already been mentioned. One such reason is the shutdown of the CLR by a host application. At that time, finalizers must run in an allotted amount of time. You can also programmatically suppress a finalizer with the GC.SuppressFinalize method. An asynchronous exception, such as a thread abort exception, can cause a finalizer not to run.

Multithreading Finalizable objects are multithreaded. Object code and the finalizer execute on different threads. For this reason, certain activities should be avoided in the finalizer. Most notably, never access thread-local storage associated with the object in the finalizer. Because the thread context has changed, using thread local storage in the finalizer is inappropriate.

The TLS sample application contains the following code that uses thread local storage. This is a Windows Forms application. A reference to a StreamWriter object is placed into the TLS table for each thread. Various threads accessing the same TMonitor object will own a different StreamWriter reference in the TLS table. Therefore, each thread will write status messages to a different file. In the sample application, two threads are accessing a TMonitor instance. In the destructor of the TMonitor type, a StreamWriter is retrieved from TLS and closed. Which StreamWriter is closed? Is this the StreamWriter of the first or second thread? The answer is neither. The destructor is running on the finalizer thread, which is unrelated to the other threads. This thread has no StreamWriter reference, and an error is reported. In Microsoft Visual Studio 2005, Console.WriteLine in a Windows Forms application displays in the Output window:

 class TMonitor {     public void WriteFile()     {         StreamWriter sw = Thread.GetData(Form1.Slot)             as StreamWriter;         if (sw == null)         {             sw = new StreamWriter(string.Format(                 @"C:\{0}File.txt",                 Thread.CurrentThread.Name),                 true);             Thread.SetData(Form1.Slot, sw);         }         sw.WriteLine(DateTime.Now.ToLongTimeString());     }     ~TMonitor()     {         StreamWriter sw = Thread.GetData(Form1.Slot)             as StreamWriter;         if (sw != null)         {             sw.Close();         }         else         {             Console.WriteLine("Error in destructor");         }     } } 

The preceding application is for demonstration purposes only. In a finalizer, you should not reference other managed objects.

One finalizer thread There is a single finalizer thread that services the FReachable queue. It calls pending finalizers of finalizable objects. The finalizer thread is different from other threads that might be accessing the object methods. Do not change the context of the finalization thread. This is not your thread. Changing the context of the finalization thread can have an adverse effect on the finalization process.

Finalizers and virtual functions Do not call virtual functions from finalizers. This can cause unexpected behavior such as inadvertent leaks and resources not being cleaned up. If overridden, there is no assurance that the derived class will provide the appropriate implementation. This is especially true for classes published in a class library, in which the developer of the derived class may have limited knowledge of the base class.

Look at the following sample code:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             YClass obj=new YClass();         }     }     public class ZClass {         protected virtual void CloseResource() {             Console.WriteLine("Closing ZClass resource.");         }         ~ZClass() {             CloseResource();         }     }     public class YClass: ZClass {         protected override void CloseResource() {             Console.WriteLine("Closing YClass resource.");         }         ~YClass() {             CloseResource();         }     } } 

In the preceding code, the finalizer in the ZClass calls the CloseResource method, which is a virtual method. YClass is derived from ZClass and overrides the CloseResource method. An instance of the derived class is created in the Main method. At garbage collection, the following messages are displayed:

 Closing YClass resource. Closing YClass resource. 

Yes, CloseResource of the derived class is called twice, which creates a couple of problems. First, an exception could be raised on the second attempt to close the resource. Second, the ZClass resource is not removed and leaks.

Order of finalization Garbage collection is not performed in a guaranteed sequence. For this reason, do not access another managed object in the finalizer. There is no assurance that the other managed object has not been collected and removed from memory.

In the following code, the ZClass has a StreamWriter field. In the finalizer, the StreamWriter is closed. Normally, this is perfectly acceptable behavior, but not in a finalizer. When the application exits, an exception is raised in the finalizer. The StreamWriter object was collected before the current object. Therefore, an exception is raised when an instance method of the StreamWriter class is called in the finalizer.

 public class ZClass {     public ZClass()     {         sw = new StreamWriter("test.txt");     }     ~ZClass()     {         sw.Close();     }     private StreamWriter sw; } 

Resurrection Zombies do exist. Yikes! Like any object, finalizable objects are created on the managed heap. When there are no outstanding references to the object, a finalizable object is submitted for garbage collection. At that time, the object is dead. However, the object is resurrected to be placed in the FReachable queue. After the finalizer is called, the object is removed from the FReachable queue. It is removed from memory during the next garbage collection. At that time, the object is dead again.

You can intentionally, or more likely inadvertently, resurrect an object permanently during the finalization process. At that time, the object is resurrected without the finalizer attached. Therefore, the resurrected object is not completely returned to the live status. The object is a zombie. Zombies pose a distinct problem because of their dangling finalizers. Because the finalizer is not called, proper cleanup is not performed when the zombie is later collected. The result could be a memory leak, a resource not being released, or other related problems. You can manually reconnect the finalizer with the GC.ReRegisterForFinalize method. Afterward, the object is normal and is no longer a zombie.

Another problem with resurrection is that the original finalizer has executed. This could render the object unusable because needed resources might have been relinquished.

During finalization, a common cause of accidental resurrection is the creation of a new reference to the current object. At that moment, the object is resurrected. Despite being resurrected, the finalizer of the object runs to completion. In the following code, the current object is directly referenced in the finalizer. An indirect reference is more likely. Referencing another managed object in a finalizer can have a domino effect and should be avoided. That object might access another managed object, which might reference another object and so on until someone references the current object. Voilà—resurrection has occurred. This is a compelling reason to avoid referencing managed objects in a destructor.

 using System; using System.IO; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             obj=new ZClass();             obj.TimeStamp();             obj=null;             GC.Collect();             GC.WaitForPendingFinalizers();             obj.TimeStamp();         }         public static ZClass obj;     }     public class ZClass {         public ZClass() {             sw=new StreamWriter("test.txt");         }         public void TimeStamp() {             sw.WriteLine(DateTime.Now.ToLongTimeString());         }         ~ZClass() {             Starter.obj=this;             sw.Close();             sw=null;         }         static private StreamWriter sw;     } } 

The first statement in the ZClass destructor resurrects the object. It assigns the object reference to a static field of the Starter class. Because there is again an outstanding reference, the object is resurrected. The underlying file of the StreamWriter instance is then closed. In Main, an instance of the ZClass is created. The instance is assigned to a static field of the Starter class. Afterward, the reference is assigned null, and garbage collection is forced. This kills and then resurrects the object. The next call on the object raises an exception because the underlying file has been closed in the previous finalization.

The following code corrects the problem and successfully resurrects the object. The first statement confirms whether the CLR is shutting down. If true, the managed application is exiting. In that circumstance, there is no reason to resurrect the object while the application domain is being unloaded. At that time, static objects are submitted to garbage collection. For that reason, the static object (sw) is accessible only when the application domain is not being unloaded. In the if block, the object is resurrected and the finalizer is reattached.

 ~ZClass() {     if(Environment.HasShutdownStarted==false) {         Starter.obj=this;         sw.Close();         sw=null;         GC.ReRegisterForFinalize(this);     } } 

The TimeStamp method of the previous code must also be updated. The finalizer sets the sw object, which is a StreamWriter instance, to null. This is checked in the revised TimeStamp method. If null, the sw reference is reinitialized. The file resource is then available again for writing:

 public void TimeStamp() {     if(sw==null) {         sw=new StreamWriter("test.txt",true);     }     sw.WriteLine(DateTime.Now.ToLongTimeString());     sw.Flush(); } 

This is the entire code of the revised application:

 using System; using System.IO; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             obj=new ZClass();             obj.TimeStamp();             obj=null;             GC.Collect();             GC.WaitForPendingFinalizers();             obj.TimeStamp();         }         public static ZClass obj;     }     public class ZClass {         public ZClass() {             sw=new StreamWriter("test.txt", true);         }         public void TimeStamp() {             if(sw==null) {                 sw=new StreamWriter("test.txt",true);             }             sw.WriteLine(DateTime.Now.ToLongTimeString());             sw.Flush();         }         ~ZClass() {             if(Environment.HasShutdownStarted==false) {                 Starter.obj=this;                 sw.Close();                 sw=null;                 GC.ReRegisterForFinalize(this);             }         }         static private StreamWriter sw;     } } 

Finalizers and reentrancy A finalizer is reentrant. The best example of this is complete resurrection, in which the finalizer is reattached. In that circumstance, the finalizer is called at least twice. When a finalizer is called more than once, resurrection is the likely culprit. Redundant calls on a finalizer should not cause a logic error or an exception.

Deep object graph A deep object graph can make garbage collection more expensive. Roots and branches of the object graph can be anchored with a finalizable object. As mentioned, the lifetime of a finalizable object is extended to encompass at least two garbage collections. All objects, even nonfinalizable objects, along the path of the finalizable object have their lives similarly extended. Therefore, one finalizable object can keep several other objects from being garbage collected. Deeper graphs by definition have longer branches and can extend the problem.

Finalizer race condition The finalizer can execute at the same time as other functions of the finalizable object. Because the finalizer executes on a dedicated thread, other functions of the finalizable object could be running when finalization starts. This can cause a race condition to occur between normal operations and the finalization. Standard thread-synchronization techniques can protect against a finalization race condition.

In the following code, MethodA is called on a ZClass instance. MethodA has a built-in delay. Shortly thereafter, the object is set to null and collected. The finalizer then runs simultaneously with MethodA. The race has begun! The results are usually unpredictable.

 public class Starter{     public static void Main(){         Thread t=new Thread(             new ThreadStart(MethodA));         obj=new ZClass();         t.Start();         obj=null;         GC.Collect();     }     private static void MethodA() {         obj.MethodB();     }     private static ZClass obj; } public class ZClass {     public void MethodB() {         Console.WriteLine("ZClass.MethodB Started");         Thread.Sleep(1500);         Console.WriteLine("ZClass.MethodB Finished");     }     ~ZClass() {         Console.WriteLine("ZClass.~ZClass Started");         // D'tor operation         Console.WriteLine("ZClass.~ZClass Finished");     } } 

Constructors An exception in the constructor does not deter the object from being finalized. The finalizer is called regardless of the success of the constructor, which can create an interesting dilemma in which an object that is unsuccessfully constructed is still finalized. Cleanup of a partially constructed object can pose risks.

The following code demonstrates the problems that can occur when an exception is raised in a constructor. The exception is caught by the exception handler in Main. At that time, the object exists but is not correctly initialized. Despite the earlier exception in the constructor, the finalizer is called as the application exits.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             try {                 ZClass obj=new ZClass();             }             catch {             }         }     }     public class ZClass {         public ZClass() {             Console.WriteLine("ZClass c'tor");             throw new Exception("Error");         }         ~ZClass() {             Console.WriteLine("ZClass d'tor");         }     } } 

The following code resolves the problem of a partially constructed finalizable object. If the constructor does not complete successfully, GC.SuppressFinalize prevents the finalizer from being called later. In addition, a flag is maintained that indicates the state of the object. The bPartial flag is set to true if the constructor fails, which is checked in instance methods. If the flag is true, the methods raise an exception because the object might not be stable.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             try {                 ZClass obj=null;                 obj=new ZClass();                 if(obj!=null){                     obj.MethodA();                 }             }             catch(Exception ex) {                 Console.WriteLine(ex.Message);             }         }     }     public class ZClass {         public ZClass() {             try {                 Console.WriteLine("ZClass c'tor");                 throw new Exception("Error");             }             catch {                 GC.SuppressFinalize(this);                 bPartial=true;             }         } public void MethodA() {             if(bPartial) {                 throw new Exception("Partial construction error");             }             Console.WriteLine("ZClass.MethodA");         }         ~ZClass() {             Console.WriteLine("ZClass d'tor");         }         private bool bPartial=false;     } } 

Console and finalization You can safely use the Console class in a finalizer. The Console class is exempted from the rule about not using managed classes in finalizers. It is especially written for use during finalization.




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

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