C++ Programmer's Cookbook

{C++ 基础} {C++ 高级} {C#界面,C++核心算法} {设计模式} {C#基础}

.NET Framework Resource Management

.NET Framework Resource Management

Overview

This white paper discusses details of resource management in components written for the Common Language Runtime (CLR).

Contents

1.   Background. 3

1.1  Garbage Collection. 3

1.2  Finalization. 3

1.2.1 Controlling Finalization Queue. 4

2.   Managing Scarce Resources. 5

2.1  Unmanaged Resources. 5

2.2  Managed Resources. 5

2.3  Releasing Unmanaged Resources. 5

2.4  Using IDisposable Types. 6

3.   Designing Cleanup Code. 7

3.1  Simple Types. 8

3.2  Finalizable Types. 8

3.3  Disposable Types. 8

3.4  Disposable and Finalizable (Both) Types. 10

3.4.1 Dispose Pattern. 10

3.5  Implementation of Dispose-Time. 11

3.5.1 Suppressing Finalization. 11

3.6  Implementation of Finalize-Time. 12

3.7  Threading Issues. 12

3.8  Versioning. 13

3.9  Inheritance and Resource Management. 13

3.9.1 Inheriting from Both Types 14

3.9.2 Inheriting from Disposable Types 14

3.9.3 Inheriting from Finalizable or Simple Types 15

3.9.4 Finalizer Implementation Details 16

4.   Performance Implications. 16

5.   Advanced Resource Management Scenarios. 17

5.1  Resource Collector Pattern. 17

5.2  IEnumerable with expensive resources. 17

5.3  Resources Referenced from Unmanaged Memory. 18

 


1.     Background

Historically, developers have had to be very careful when allocating resources that require explicit released. For example in C++, all memory allocated using operator new needs to be released by a call to delete. This has been a source of numerous code defects.

CLR (Common Language Runtime) provides support for automatic memory management that frees developers from the difficult task of managing allocated memory. Unfortunately, memory is not the only resource that needs to be released after use. There are others, including database connections, system handles, etc. These unmanaged resources are not understood by the CLR and may need to be released manually.

Sections 1 and 2 of the document are intended for developers using components that allocate unmanaged resources. The rest of the document is intended mainly for developers implementing such components. If you are only a user of such components, you don’t need to be concerned with most of the issues described below section 2.

1.1     Garbage Collection

The .NET Framework's garbage collector manages the allocation and release of memory for your application. Each time you use the new operator to create an object, the runtime allocates memory for the object from the managed heap. As long as memory space is available in the managed heap, the runtime continues to allocate space for new objects. However, memory is not infinite. Eventually, the garbage collector must perform a collection in order to free some memory. The garbage collector's optimizing engine determines the best time to perform a collection, based upon the allocations being made. When the garbage collector performs a collection, it checks for objects in the managed heap that are no longer being used by the application and performs the necessary operations to reclaim their memory.

1.2     Finalization

For the majority of the objects that your application creates, you can rely on the CLR’s garbage collector to automatically perform all the necessary memory management tasks. However, some objects encapsulate unmanaged resources and those resources must be released explicitly. Although the garbage collector is able to track the lifetime of an object that encapsulates an unmanaged resource, it does not have specific knowledge about how to clean up the resource. For these types of objects, the .NET Framework provides the Object.Finalize method, which allows an object to clean up its unmanaged resources properly when the garbage collector reclaims the memory used by the object. By default, the Finalize method does nothing. If you want the garbage collector to perform cleanup operations on your object before it reclaims the object's memory, you must override the Finalize method in your class. You must use destructor syntax for your finalization code in the C# and the C++ with Managed Extensions.

The garbage collector keeps track of objects that have Finalize methods, using an internal structure called the finalization queue. Each time your application creates an object that has a Finalize method, the garbage collector places an entry in the finalization queue that points to that object. The finalization queue contains entries for all the objects in the managed heap that need to have their finalization code called before the garbage collector can reclaim their memory.

Implementing Finalize methods or destructors can have a negative impact on performance, so you should avoid using them unnecessarily. Reclaiming the memory used by objects with Finalize methods requires at least two garbage collections. When the garbage collector performs a collection, it reclaims the memory for inaccessible objects without finalizers. At this time, it cannot collect the inaccessible objects that do have finalizers. Instead, it removes the entries for these objects from the finalization queue and places them in a list of objects marked as ready for finalization. Entries in this list point to the objects in the managed heap that are ready to have their finalization code called. A special runtime thread becomes active and calls the Finalize methods for the objects in this list and then removes the entries from the list. A future garbage collection will determine that the finalized objects are truly garbage because they are no longer pointed to by entries in the list. In this future garbage collection, the objects' memory is actually reclaimed.

The following diagram shows the GC state transition that an object goes through during its lifetime.

1.2.1     Controlling Finalization Queue

The runtime provides two methods to control objects’ eligibility for finalization, GC.SuppressFinalize and GC.ReRegisterForFinalize. GC.SuppressFinalize removes an object from the list of objects that will be finalized when they are no longer reachable, and GC.ReRegisterForFinalize takes an object that was previously suppressed, and adds it back to the list.

GC.SuppressFinalize is used to prevent unnecessary and costly finalization after the object has been cleaned up through other means, for example through IDisposable interface. GC.ReRegisterForFinalize is used to assure resource cleanup after an already cleaned up object reacquires resources that will need to be cleaned up.

In the .NET Framework Beta 2 release, there is a very expensive security check in GC.SuppressFinalize. This check has been removed in the final release, and should not be a source of performance problems.

2.     Managing Scarce Resources

2.1     Unmanaged Resources

As we have already mentioned, unmanaged resources are resources that cannot be released by the GC directly. Examples of unmanaged resources include file handles, database connection handles, message queue handles, and native memory allocated using Marshal.AllocHGlobal.

Releasing unmanaged resources requires an explicit call to a custom API, something along the lines of closing a “handle”.

2.2     Managed Resources

Managed resources are resources that can be released by the GC directly. Examples of managed resources include String, ArrayList, and TimeSpan. Code acquiring managed resources does need to be concerned with releasing them.

Instantiating and calling methods of managed types does not always allocate only managed resources, as it may seem. It may indirectly allocate unmanaged resources.

2.3     Releasing Unmanaged Resources

Any type allocating unmanaged resources, either directly or indirectly, has to provide a mechanism to release the resources. The simplest way of releasing an unmanaged resource is to release it in the Finalizer of the type owning the resource. This approach is perfectly fine for releasing resources that do not have limits on how many you can acquire and do not have a cost associated with keeping them acquired longer than absolutely necessary. Unfortunately, most unmanaged resources are not limitless and not free; rather they are scarce. Database connection is a perfect example of a scarce resource; you can open only a limited number them.

Finalizers are not very good at releasing scarce resources. They run too late. GC starts collection, and runs finalizers, when it detects memory allocation problems. It is not able to detect unmanaged resource allocation problems.

The solution is to provide clients of types that acquire scarce resources with a way to explicitly release the resources. This way the client can release the resources as soon as they are not needed anymore. For example:

try{

MessageQueue queue = new MessageQueue(path);
queue.Send(message);

}

finally{

queue.Dispose(); // explicitly release queue handle

}

 

To formalize and organize the process of releasing scarce resources, we have defined a couple of design guidelines and the IDisposable interface.

public interface IDisposable {

   public void Dispose();

}

This defines a basic interface that declares the Dispose method, which is designed to be a common cleanup routine. When you implement this interface on a type, you are advertising that instances of that type allocate scarce resources. Some scenarios require clients to explicitly release the resources and some don’t. When in doubt, play it safe and always release the resource by making sure Dispose is called after the object is no longer needed.

For example, common implementations of server applications instantiate a new resource in every request, use the resource, and then release it. Such implementations will result in the cycle of allocate->use->release being repeated many times during the lifetime of the application. If the resource is scarce, it is desirable for the cycle to be as short as possible to maximize the number of requests that the application can process per unit of time. Calling Dispose() immediately after the resource is no longer needed will result in maximum throughput of the application.

Also, some multi-user client applications using resources that are shared among users should dispose the resources as soon as possible. This will prevent problems in which some users are denied access to a resource only because another user’s resource is waiting in the finalization queue. Holding a database connection open for longer than needed is a good example of such a programming mistake.

Single user applications, which don’t share scarce resources with other applications, don’t require such urgency in disposing resources. For example, a command line tool that creates a MessageQueue, sends a message, and terminates doesn’t need to explicitly dispose the MessageQueue instance. Please note that if the same code were executed in a server request, it would certainly cause a throughput problem.

2.4     Using IDisposable Types

Instances of types implementing the IDisposable interface need to be handled very carefully, especially in the presence of possible exceptions. For example, the following code may actually result in the resource not getting disposed

MessageQueue queue = new MessageQueue(“server\\queueName”);

queue.Send(“Hello World”);

queue.Dispose();

 

If the Send operation throws an exception, the call to Dispose on the following line will not get executed.

A better way to implement the code is to dispose resources in a finally block, which always gets executed, even when an exception is thrown.

MessageQueue queue;

try{

queue = new MessageQueue(“server\\queueName”);

   queue.Send(“Hello World”);

}

finally{

   queue.Dispose()

}

 

The codes become more robust but also a bit less readable. The readability problem is even more pronounced in the case of two resources that need to be disposed, since such scenarios require nested try-finally blocks.

MessageQueue source;

MessageQueue destination;

try{

source = new MessageQueue(“server\\sourceQueue”);

   Message message = source.Receive();

   try{

       destination = new MessageQueue(“server\\destinationQueue”);

      destination.Send(message);

}

finally{

       destination.Dispose();

   }

}

finally{

source.Dispose();   

}

 

Unfortunately, the code above lost a lot of its original simplicity. This is why some languages, including C#, introduced the using statement. The statement has semantics similar to the code above without sacrificing simplicity. It expands to multiple try-finally blocks and automatically calls Dispose() on IDisposable objects created inside the using clause.

using(

MessageQueue source = new MessageQueue(“server\\sourceQueue”),

   destination = new MessageQueue(“server\\destinationQueue”)

){

   Message message = source.Receive();

   destination.Send(message);    

}

  

If you work with languages that don’t provide support similar to like the using statement, you should implement the fully expanded code manually.   

3.     Designing Cleanup Code

When designing a component that allocates scarce resources, you will encounter one of four main variations common to all resource management implementations. You need to identify which variation best fits your component and implement your resource cleanup code accordingly.

3.1     Simple Types

This variation applies to types that hold references to only other managed objects that do not implement IDisposable and do not have Finalizers. This includes ensuring that no internal container, for example ArrayList, stores objects that require explicit resource cleanup. In such case, you don’t need to do anything special in terms of resource management.

Example:

public class SimpleType{

   ArrayList names = new ArrayList();

   …

}

3.2     Finalizable Types

This variation should be implemented carefully, only after fully considering all the ramifications. Types implementing this variation may cause system starvation if scarce resources are left allocated for longer than needed. In most cases the best coarse of action is to implement the full Disposable and Finalizable (Both) TypesDisposable and Finalizable (Both) Types variation described in section 3.4.

Only types that acquire unmanaged but not scarce resources and zero or more Simple types should implement this variation. For example, a type that allocates only a very small blob of unmanaged memory falls into this category. In such cases having a finalizer ensures that the memory is released but does not oblige clients to call Dispose. This is a rare case and should not be used in most implementations.

Example:

public class FinalizableOnly{

   IntPtr nativeMemory = Marshal.AllocHGlobal(4);

 

   ~FinalizableOnly(){

       Marshal.FreeHGlobal(nativeMemory);

   }

 

   …

}

3.3     Disposable Types

Implement this variation for types that allocate, directly or indirectly, only managed resources, and for which most classes derived from the type will also allocate only managed resources. System.Web.UI.Control is an example. The Web Control base class has no unmanaged resources, and most classes derived from it won't either. This is why it does not need a finalizer. However, it is common for such controls to have other managed resources (SqlConnection, MessageQueue, etc.) that implement IDisposable interface, so it implements IDisposable as well to allow these to be cleaned up early.

Example:

public class DisposableOnly: IDisposable{

   MessageQueue queue;

   bool disposed = false;

 

public DisposableOnly(string path){

queue = new MessageQueue(path);

   }

 

   public virtual void Dispose(){

       if(!disposed){

queue.Dispose();

queue = null;

          disposed = true;

       }

   }

 

   public void SendMessage(string message){

       if(disposed){

          throw new ObjectDisposedException(this.ToString());

}

 

       queue.Send(message);

   }

}

 

After Dispose() is called, objects are free to throw ObjectDisposedException from any instance method except Dispose(). Dispose() can be called multiple times and should never throw ObjectDisposedException. An alternative is to allow an object to be resurrected by reacquiring resources when a method is called on already disposed object. For example:

public class DisposableResurrectableOnly: IDisposable{

   MessageQueue queue;

   bool disposed = false;

 

public DisposableOnly(string path){

queue = new MessageQueue(path);

   }

 

   public virtual void Dispose(){

       if(!disposed){

queue.Dispose();

queue = null;

          disposed = true;

       }

   }

 

   public void SendMessage(string message){

       if(disposed){

          Ressurect();

}

 

       queue.Send(message);

   }

 

   protected void Ressurect(){

       queue = new MessageQueue();

       disposed = false;

}

}

 

Some types may want to provide an additional cleanup method with a domain specific name. For example, SqlConnection class provides the method Close. Such method should just call Dispose().

3.4     Disposable and Finalizable (Both) Types

These are types that allocate scarce unmanaged resources. Such types need both the Dispose() method to allow explicit cleanup of the resource and the finalizer as a backup in cases when Dispose() does not get called.

3.4.1     Dispose Pattern

All Both types should declare the following methods and implement the IDisposable interface.

protected virtual void Dispose(bool disposing)
protected void virtual Finalize()
public void Dispose()

 

The Dispose() method should call Dispose(true) followed by GC.SuppressFinalize only. The finalizer should call Dispose(false) and nothing else. This allows resource management code to be well structured and localized. During development of the .NET Framework, we found such design optimal in terms of readability and maintainability. We believe it helps to minimize the number of code defects in complicated resource management code.

Example:

public class BothType: IDisposable{

  

   public void Dispose(){

       Dispose(true);

GC.SupressFinalize(this);

   }

  

   ~BothType(){

       Dispose(false);

}

 

   protected virtual void Dispose(bool disposing){

       if(disposing){

          … 

       }

       …

   }

 

   …

}

 

The method Dispose(bool) can execute in two distinct scenarios:

  1. When called directly or indirectly by the user code, the parameter disposing equals true. We will call this scenario “dispose-time”.
  2. When called by the Common Language Runtime through the finalizer, the parameter disposing equals false. We will call this scenario “finalize-time”.

You need to be careful to implement the scenarios correctly. There are some restrictions on which operations can be executed at finalize-time and which at dispose-time. Sections 3.5 and 3.6, below, describe details of implementing those two scenarios.

3.5     Implementation of Dispose-Time

Implementing dispose-time code is relatively straightforward. You can touch any kind of managed object or unmanaged resources. There is no difference between what dispose-time code can do and what any other method can do. As you will see in a moment, this is not the case for finalize-time code. 

Dispose-time code should call Dispose() on all owned objects that implement the IDisposable interface. By “owned,” I mean objects whose lifetime is solely controlled by the container. In cases where ownership is not as straightforward, techniques such as Handle Collector, described in section 5.15.1, can be used. 

Dispose-time code should also set references of all owned objects to null, after disposing them. This will allow the referenced objects to be garbage collected even if not all references to the “parent” are released. It may be a significant memory consumption win if the referenced objects are large, such as big arrays, collections, etc. 

protected virtual void Dispose(bool disposing){

// dispose-time code

if(disposing){

queue.Dispose();

queue = null;

      

   }

  

// finalize-time code

   …

 

   disposed = true;

}

 

3.5.1     Suppressing Finalization

Because the cleanup code executed at dispose-time is a superset of the code executed at the finalize-time, there is no need to call the finalize-time code during object finalization after the object has been disposed. Moreover, keeping objects that don’t need to be finalized in the finalization queue has a cost associated with it. This is why the Dispose() method should call GC.SuppressFinalize, which removes the object from the finalization queue and thus prevents unnecessary finalization.

public void Dispose(){

Dispose(true);

GC.SupressFinalize(this);

}

 

Some types can be used after being disposed. Such types need to call GC.ReRegisterForFinalize when they reacquire resources. 

3.6     Implementation of Finalize-Time

Implementing the finalize-time code path is trickier. There are two kinds of objects that cannot be referenced during finalization. Finalizable or Both objects belonging to the same finalization graph must not reference each other in their finalizers. Also, statically referenced Finalizable or Both objects, which normally can be accessed, cannot be accessed when the application domain or the runtime is shutting down.

The order in which objects in a graph are finalized is undefined. This means finalize-time code should not call methods on non-statically referenced Finalizable or Both objects, including the Dispose() method. Such object may already be finalized and may throw when called. Please note that a method on Simple or Disposable object may in turn call a method on one of the objects mentioned above.

Usually, statically referenced objects can be accessed at finalize-time, but not always. If the runtime is shutting down, the GC tries to collect and possibly finalize all objects, regardless of they are still referenced or not. This means that statically referenced objects can be finalized before non-statically referenced ones. In such situations, the finalizer may fail because the statically referenced object may have already been finalized. If you want to access a statically referenced Finalizable or Both object in the finalize-time code path, you should check the value of Environment.HasShutdownStarted. If the value is true, you should not access the static.    

protected virtual void Dispose(bool disposing){

// dispose-time code

if(disposing){

       …     

   }

  

// finalize-time code

       CloseHandle();

  

if(!Environment.HasShutdownStarted){

// the following line would fail if statically referenced items of

// the Debug.Listeners collection are finalized.

Debug.WriteLine(“Finalizer called”);

}

 

 

   disposed = true;

}

3.83.7     Threading Issues

You should consider whether your cleanup code should be thread-safe or not. The protected Dispose(bool) method cannot be called from both the user thread and the finalizer thread at the same time. It can be called from multiple user threads, though this is not common. Dispose thread-safety issues are no different than thread-safety issues of any other method. If your type is thread safe, make the cleanup code thread safe. If your type is not thread safe, don’t make cleanup code thread-safe either.

Components that are written to be thread safe will often pay a price in performance relative to non-thread-safe components. This is because the locking code or concurrency-friendly algorithms needed to preserve thread safety usually come at a performance cost relative to code which does not need to protect against concurrent access. Knowing that most usage of components is in a single-threaded environment, the designers of the .NET Framework have opted in most cases to leave thread safety enforcement up to the clients.

3.93.8     Versioning

Once you ship a type with a specific resource management support (Simple, Disposable, Finalizable, or Both), there are some significant concerns with versioning to consider. Although adding new resource management support options[1] is a version compatible change, it does introduce some complexity for code that derives from yours. Specifically, if version 1.0 of a type was Disposable, and v 2.0 becomes Both, you now are faced with potentially 3 cleanup methods (Dispose, Dispose(bool), and Finalize) that can be overridden. In addition, the order in which the derived code gets called in relation to the base object changes subtly for each method.

The recommendation is to avoid changing the cleanup type of the object once a version has shipped. It is best to err on the side of more cleanup code than necessary (Both, preferably) than be faced with adding cleanup in the next version of a component.

public class PlayingSafe: IDisposable{

   IntPtr nativeMemory = Marshal.AllocHGlobal(4);    

 

   public void Dispose(){

       Dispose(true);

GC.SupressFinalize(this);

   }

  

   ~ PlayingSafe(){

       Dispose(flase);

}

  

   // Despite the fact that this type may not need to be Disposable,

   // it implements the Both variation. This will help derived types

   // in implementing clean resource management code.

   protected virtual void Dispose(bool disposing){

       Marshal.FreeHGlobal(nativeMemory);

   }

 

   …

}

3.103.9     Inheritance and Resource Management

When deriving from classes implementing some resource management methods, you should always try to override the method that reflects the highest level of cleanup support the base class offers.

3.10.13.9.1     Inheriting from Both Types

If there is a virtual void Dispose(bool) method on the base class, you are probably inheriting from Both type. In such case, you should override that method for both dispose-time and finalize-time cleanup.

public class DerivedFromBoth: Both{

   ManagedResource addedManaged;

   NativeResource addedNative;

 

protected override void Dispose(bool disposing){

       try{

// additional dispose-time

if(disposing){

          addedManaged.Dispose();

          addedManaged = null;

       }     

      

       // additional finalize-time

       CloseHandle(addedNative);

       }

       finally{

          // old dispose and finalize time.

          // alternatively, you can not call it bu then this method

          // is responsible for cleaning the base class.

base.Dispose(disposing);

       }

}

}

 

You should not override the finalizer or the Dispose() methods, even if they are virtual. Dispose() may be virtual if one of the classes your base class descends from is a Disposable only type.

 

3.10.23.9.2     Inheriting from Disposable Types

If only a Dispose() method is present on the base class, you are probably dealing with Disposable type. If you are not adding any support for finalize-time cleanup, the new dispose-time logic should go in an override of the Dispose() method.

public class StillDisposableOnly: DisposableOnly{

 

   public override void Dispose(){

       if(!disposed){ // disposed is a field in the base calss

          try{

// do additional ceanup here

             …

          }

          finally{

             base.Dispose();

          }

       }

   }

}

 

If you need to add finalize-time cleanup, then implement the Both pattern by providing a protected virtual Dispose(bool) method and call it from Finalize and Dispose with the correct arguments. You must call base.Dispose() from your new Dispose(bool).

public class NowBoth: DisposableOnly{

         

          // Change Dispose variation to Both variation

          // Instead of doing cleaup in Dispose(), call Dispose(bool)

public override void Dispose(){

       Dispose(true);

 

       // base.Dispose() will call GC.SupressFinalize

}

 

// added fianlizer

   ~ NowBoth(){

       Dispose(flase);

}

 

          protected virtual void Dispose(bool disposing){

             

              if(disposing){

                 base.Dispose();

              }

 

              // added support for finalize-time cleanup

              CloseHandle();

          } 

}

 

3.10.33.9.3     Inheriting from Finalizable or Simple Types

If no Dispose() method is present, you are deriving from the Simple or Finalizable type. If you don’t plan to add support for dispose-time cleanup, the scenario is trivial; just override the finalizer. If you want to add support for dispose-time cleanup, you must implement IDisposable. However, since the base type doesn't support IDisposable, it may be common for consumers of your class not to call Dispose.

Public class DisposableDerivedFromSimple: SimpleType, IDisposable{

   public virtual void Dispose(){

       …

   }

}

 

class Client{

   void Foo(){

       SimpleType instance = Factory.Create();

      

       // who and how will call Dispose if Factory.Create returns

       // instance of DisposableDerivedFromSimple?

   }

}

 

Another problem with introducing dispose-time logic to classes derived from Finalizable types is that the derived class cannot call GC.SupressFinalize in its implementation of Dispose(). The reason is that this would prevent the base finalize-time code from running because the finalizer of the base class cannot be called explicitly in Dispose(bool).  

   public class BothFromFinalizable: FinalizableOnly, IDisposable{

         

public void Dispose(){

          Dispose(true);

          // cannot call GC.SupressFinalize!!!

}

 

       ~ BothFromFinalizable(){

          Dispose(flase);

}

 

          protected virtual void Dispose(bool disposing){

              if(disposing){

                 base.Dispose();

              }

 

              // how would I call base.Fianlize()?!!!

          } 

}

 

Because of the problems discussed above, you should consider whether inheriting from the type can be avoided. We recommend that you use other design alternatives such as containment, delegation, etc., instead of deriving disposable type form a non-disposable base class.

3.10.43.9.4     Finalizer Implementation Details

To facilitate the four versions of cleanup logic that types may offer, C# and other languages have introduced changes into the languages that make cleanup easier. For example C# generates the following code when you add a finalizer to your type.

protected override void Finalize() {

   try {

       // body of your destructor goes here

   }

   finally {

       base.Finalize();

   }

}

 

This ensures that the base class finalization code will be called in the case of Finalizable types, but it does not help Both types. This same code is generated for all destructors, as C# doesn't check whether the object is a Both type.

4.     Performance Implications

When Dispose is not called on a Both object, the object is allowed to finalize. This is fairly expensive. The GC must do extra bookkeeping work to invoke the finalize method. Also, some applications have many threads creating objects, and because only one thread can be executing the finalizers, the finalization thread may have problems handling large numbers of objects. This is why creating many objects with Finalize methods on different threads and letting them to get finalized can lead to poor performance.

Because of this, you shouldn't add a Finalize method to a type unless the type allocates unmanaged resources or you anticipate most derived types having to allocate unmanaged resources. However, if you do decide to implement a Finalize method, it is highly recommended that you implement the Both pattern. This will allow users to call Dispose and avoid having the Finalize method invoked.

System.ComponentModel.Component implements the Both variation. The type does not allocate unmanaged resources itself, but we expected most types derived from Component to allocate such resources. Types that don’t want to take the performance hit associated with the pattern should implement IComponent instead of inheriting from Component.

5.     Advanced Resource Management Scenarios

5.1     Resource Collector Pattern

There are scenarios in which the ownership of Disposable or Both objects is not well defined. For example, the reference to an object may be handed off to multiple clients. In such scenarios, it is very difficult, if not impossible, to dispose the scarce resource as soon as it is no longer needed. None of the clients knows when the other clients are done with the object.

One set of solutions to the problem relies on letting the finalizer collect the resources and just forcing the GC to collect more often than it normally would. We call such solutions Resource Collectors. Resource Collectors helps to minimize the time between when the resource is available for collection and the time when the finalizer actually runs and releases the resource. One drawback of such an approach is that forcing collection, if abused, may actually lead to performance loss. Good implementations of the pattern employ clever algorithms determining when forced collection should be executed.

One such implementation, a component called HandleCollector, can be downloaded from the GotDotNet site (http://GotDotNet). The collector maintains a counter of “live” resources. The counter is incremented when a resource is allocated and is decreased when the resource is released by the finalizer. When a new resource is about to be allocated and the counter is greater than an application specified threshold, collection is forced by a call to GC.Collect.

A degenerated version of a Resource Collector implementation uses a timer to force collection at specified intervals. Such an implementation is discouraged. The implementation described above is much better at forcing collection when needed and avoiding unnecessary collections which may significantly impair performance and scalability.

5.2     IEnumerable with expensive resources

Often IEnumerable types allocate scarce resources in GetEnumerator(). For example, the IEnumerator returned from GetEnumerator may hold a reference to an expensive array of Disposable objects. Such IEnumerator types should implement the IDisposable interface to allow explicit cleanup.

internal class ExpensiveEnumerator: IEnumerator, IDisposable{

   …     

}

 

public class ExpensiveCollection: IEnumerable{

   public IEnumerator GetEnumerator(){

       return new ExpensiveEnumerator();

}

   …

}

 

Some languages, for example C# and VB.NET, will call Dispose on enumerators that implement IDisposable when the foreach statement completes.

ExpensiveCollection expensive = new ExpensiveCollection();

foreach(ExpensiveItem in expensiveCollection){

   …

} // Dispose will be called on the IEnumerator when the loop terminates   

5.3     Resources Referenced from Unmanaged Memory

There are situations in which an object in unmanaged memory holds a reference to a managed object. Such managed objects cannot be collected, even when there are no managed references to them, without an explicit call freeing the last reference from the unmanaged object. The problem can be solved by containing the object that needs to be referenced from unmanaged memory inside a wrapper. The internal object is never handed off to the managed client code. The wrapper is, and when disposed or finalized, frees the reference from the unmanaged object.

posted on 2006-03-10 15:37 梦在天涯 阅读(784) 评论(2)  编辑 收藏 引用 所属分类: C#/.NET

评论

# re: .NET Framework Resource Management 2006-03-10 16:14 dudu

请在首页发表原创C++文章!  回复  更多评论   

# re: .NET Framework Resource Management 2006-04-14 16:22 梦在天涯

Finalize 和Dispose(bool disposing)和 Dispose() 的相同点:

这三者都是为了释放非托管资源服务的.

Finalize 和 Dispose() 和Dispose(bool disposing)的不同点:

Finalize是CRL提供的一个机制, 它保证如果一个类实现了Finalize方法,那么当该类对象被垃圾回收时,垃圾回收器会调用Finalize方法.而该类的开发者就必须在Finalize方法中处理 非托管资源的释放. 但是什么时候会调用Finalize由垃圾回收器决定,该类对象的使用者(客户)无法控制.从而无法及时释放掉宝贵的非托管资源.由于非托管资源是比较宝贵了,所以这样会降低性能.
Dispose(bool disposing)不是CRL提供的一个机制, 而仅仅是一个设计模式(作为一个IDisposable接口的方法),它的目的是让供类对象的使用者(客户)在使用完类对象后,可以及时手动调用非托管资源的释放,无需等到该类对象被垃圾回收那个时间点.这样类的开发者就只需把原先写在Finalize的释放非托管资源的代码,移植到Dispose(bool disposing)中. 而在Finalize中只要简单的调用 "Dispose(false)"(为什么传递false后面解释)就可以了.
这个时候我们可能比较疑惑,为什么还需要一个Dispose()方法?难道只有一个Dispose(bool disposing)或者只有一个Dispose()不可以吗?
答案是:
只有一个Dispose()不可以. 为什么呢?因为如果只有一个Dispose()而没有Dispose(bool disposing)方法.那么在处理实现非托管资源释放的代码中无法判断该方法是客户调用的还是垃圾回收器通过Finalize调用的.无法实现 判断如果是客户手动调用,那么就不希望垃圾回收器再调用Finalize()(调用GC.SupperFinalize方法).另一个可能的原因(:我们知道如果是垃圾回收器通过Finalize调用的,那么在释放代码中我们可能还会引用其他一些托管对象,而此时这些托管对象可能已经被垃圾回收了, 这样会导致无法预知的执行结果(千万不要在Finalize中引用其他的托管对象).



所以确实需要一个bool disposing参数, 但是如果只有一个Dispose(bool disposing),那么对于客户来说,就有一个很滑稽要求,Dispose(false)已经被Finalize使用了,必须要求客户以Dispose(true)方式调用,但是谁又能保证客户不会以Dispose(false)方式调用呢?所以这里采用了一中设计模式:重载 把Dispose(bool disposing)实现为 protected, 而Dispose()实现为Public,那么这样就保证了客户只能调用Dispose()(内部调用Dispose(true)//说明是客户的直接调用),客户无法调用Dispose(bool disposing).


范例如下:

public class BaseResource: IDisposable
{
//析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用.默认情况下,一个类是没有析构函数的,也就是说,对象被垃圾回收时不会被调用Finalize方法
~BaseResource()
{
// 为了保持代码的可读性性和可维护性,千万不要在这里写释放非托管资源的代码
// 必须以Dispose(false)方式调用,以false告诉Dispose(bool disposing)函数是从垃圾回收器在调用Finalize时调用的
Dispose(false);
}


// 无法被客户直接调用
// 如果 disposing 是 true, 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放
// 如果 disposing 是 false, 那么函数是从垃圾回收器在调用Finalize时调用的,此时不应当引用其他托管对象所以,只能释放非托管资源
protected virtual void Dispose(bool disposing)
{

// 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放
if(disposing)
{
// 释放 托管资源
OtherManagedObject.Dispose();
}


//释放非托管资源
DoUnManagedObjectDispose();


// 那么这个方法是被客户直接调用的,告诉垃圾回收器从Finalization队列中清除自己,从而阻止垃圾回收器调用Finalize方法.
if(disposing)
GC.SuppressFinalize(this);

}

//可以被客户直接调用
public void Dispose()
{
//必须以Dispose(true)方式调用,以true告诉Dispose(bool disposing)函数是被客户直接调用的
Dispose(true);
}
}

上面的范例达到的目的:

1/ 如果客户没有调用Dispose(),未能及时释放托管和非托管资源,那么在垃圾回收时,还有机会执行Finalize(),释放非托管资源,但是造成了非托管资源的未及时释放的空闲浪费

2/ 如果客户调用了Dispose(),就能及时释放了托管和非托管资源,那么该对象被垃圾回收时,不回执行Finalize(),提高了非托管资源的使用效率并提升了系统性能


可以参考SqlConnection对象的New, Open, Close(内部调用Dispose())的使用经历可以加深对他们的理解.谢谢!
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

EMail:itech001#126.com

导航

统计

  • 随笔 - 461
  • 文章 - 4
  • 评论 - 746
  • 引用 - 0

常用链接

随笔分类

随笔档案

收藏夹

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

积分与排名

  • 积分 - 1785161
  • 排名 - 5

最新评论

阅读排行榜