C# Source Code: A generic object pooling abstract base class (thread safe)
[
Home
|
Contents
|
Search
|
Reply
| Previous |
Next
]
C# Source Code
A generic object pooling abstract base class (thread safe)
By:
Andrew Baker
Email (spam proof):
Email the originator of this post
Date:
Monday, July 26, 2004
Hits:
7393
Category:
Threading/Asynchronous operations
Article:
Below is an abstract base class that can be used as a generic object pooling class. The class is thread safe and an example implementation can be found at the bottom of this post: using System; using System.Collections; using System.Timers; using System.Diagnostics; using System.Threading; namespace VBusers.Threading.ObjectPool { ///
/// A thread safe base class, designed for use as a generic object pool /// Copyright 2004 www.VBusers.com. /// By Andrew Baker ///
public abstract class ObjectPoolBase { #region Structures //private structure to store details of each pool object private struct HashTableValue { public DateTime DateAdded; public object PoolObject; public int PoolId; } #endregion #region Private variables //Stores the time the class was last accessed private DateTime _lastAccessed = DateTime.Now; private static Hashtable _objectsInUse = null; private static Hashtable _objectsAvailable = null; private int _nextKeyId = 0; //Default the garbage collection to 2 mins private static TimeSpan _garbageCollectionInterval = new TimeSpan(0, 0, 120); private static System.Timers.Timer _garbageChecker = null; //Determines if objects are automatically tested to see if they are busy during garbage collections. private bool _autoReturnObjects = false; #endregion #region Abstract (must inherit) methods ///
/// Abstract method called to create a new instance of an object to be pooled ///
///
A new instance of a pool object
protected abstract object CreateObject(); ///
/// Abstract method called to dispose of an instance of a pooled object ///
///
The object to dispose of protected abstract void DisposeObject(object obj); ///
/// Abstract method called to determine if an object can be used ///
///
The object to test ///
Returns true is the object is available, else returns false
protected abstract bool IsAvailable(object obj); #endregion #region Constructors static ObjectPoolBase() { //Create hashtables to hold pool objects _objectsInUse = Hashtable.Synchronized(new Hashtable()); _objectsAvailable = Hashtable.Synchronized(new Hashtable()); } ///
/// Make the class a must inherit class by changing the constuctor /// modifier to internal. ///
internal ObjectPoolBase() { lock(this) { if(_garbageChecker != null) { Debug.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". CreateObjectPool."); //Create a timer to check for expired objects _garbageChecker = new System.Timers.Timer(_garbageCollectionInterval.Seconds * 1000); _garbageChecker.Enabled = true; _garbageChecker.Elapsed += new ElapsedEventHandler(_garbageChecker_Elapsed); } } } #endregion #region Protected/Internal Methods ///
/// Gets the first available object from the pool. If no objects are available /// will create a new object and return this. ///
///
internal object GetObject() { _lastAccessed = DateTime.Now; //Apply lock lock(this) { try { //Structure which stores details of each pool object HashTableValue htv; //Copy the key collection so we can iterate over it without //the requirement that the hashtable does not change. object [] keys = new object [_objectsAvailable.Count]; _objectsAvailable.CopyTo( keys, 0); foreach(DictionaryEntry de in keys) { htv = (HashTableValue)_objectsAvailable[de.Key]; //Validate object if(IsAvailable(htv.PoolObject) == true) { //Object is free for use, remove object from available pool _objectsAvailable.Remove(htv.PoolId); //Add object to in use pool _objectsInUse.Add(htv.PoolId, htv); //Return object return htv.PoolObject; } else { //Object unavailable, remove it _objectsAvailable.Remove(htv.PoolId); } } //There are no free objects, create new object htv = new HashTableValue(); htv.DateAdded = DateTime.Now; htv.PoolObject = CreateObject(); //Get next reference Id _nextKeyId++; htv.PoolId = _nextKeyId; _objectsInUse.Add(htv.PoolId, htv); //Return the object return htv.PoolObject; } catch(Exception ex) { Debug.WriteLine("Error in GetObject. " + ex.ToString()); } return null; } } ///
/// Return an object to the object pool once it has finished it's task ///
///
The object to return to the pool internal void ReturnObject(object obj) { _lastAccessed = DateTime.Now; if(obj != null) { lock(this) { //Find matching object foreach ( DictionaryEntry de in _objectsInUse) { HashTableValue htv = (HashTableValue)de.Value; //Test to see if object points to same object on heap if (Object.Equals(htv.PoolObject, obj) == true ) { //Found object, remove it from objects in use _objectsInUse.Remove(htv.PoolId); //Add it to objects available _objectsAvailable.Add(htv.PoolId, htv); return; } } } } } ///
/// Periodically checks for objects that are not in use and have existed longer /// than the garbage collection time interval ///
private void _garbageChecker_Elapsed(object sender, ElapsedEventArgs e) { DateTime now = DateTime.Now; lock(this) { try { //First check for objects that can be Disposed of (are not in use). //Copy the key collection so we can iterate over it without //the requirement that the hashtable does not change. object [] keys = new object [_objectsAvailable.Count]; _objectsAvailable.CopyTo( keys, 0); foreach(DictionaryEntry de in keys) { HashTableValue htv = (HashTableValue)de.Value; DateTime dateAdded = htv.DateAdded; if(dateAdded.Add(_garbageCollectionInterval) < now) { //Object has expired _objectsAvailable.Remove(htv.PoolId); //Dispose of object DisposeObject(htv.PoolObject); htv.PoolObject = null; } } if(_autoReturnObjects == true) { //Next check to see if any objects are no longer busy //(Don't use an enumerator (as will be removing items from collection)) keys = new object [_objectsInUse.Count]; _objectsInUse.CopyTo( keys, 0); foreach(DictionaryEntry de in keys) { HashTableValue htv = (HashTableValue)de.Value; if(IsAvailable(htv.PoolObject) == true) { //Object has expired _objectsInUse.Remove(htv.PoolId); _objectsAvailable.Add(htv.PoolId, htv); } } } } catch(Exception ex) { Debug.WriteLine("Error in GarbageChecker. " + ex.ToString()); } } } ///
/// The time in milliseconds between garbage collections ///
protected TimeSpan GarbageCollectionInterval { get{return _garbageCollectionInterval;} set { _garbageCollectionInterval = value; _garbageChecker.Interval = _garbageCollectionInterval.Seconds * 1000; } } ///
/// Returns a count of the objects in use ///
protected int ObjectsInUse { get{return _objectsInUse.Count;} } ///
/// Returns a count of the objects in available for use ///
protected int ObjectsAvailable { get{return _objectsAvailable.Count;} } ///
/// Determines if objects are automatically tested to see if they are busy during garbage /// collections. ///
///
/// If this is set to false (the default) then an explicit call to ReturnObject /// is required after the object has finished it's task. ///
protected bool AutoReturnObjects { get{return _autoReturnObjects;} set{_autoReturnObjects = value;} } #endregion } } ///********************************************/// ///********************************************/// ///Example Implementation ///********************************************/// ///********************************************/// /* REMOVE THIS LINE using System; using System.Diagnostics; using System.Threading; namespace VBusers.Threading.ObjectPool.Testing { ///
/// A demonstration of how to use the object pool. ///
public sealed class MenWhoDigHolesInTheGround: ObjectPoolBase { public MenWhoDigHolesInTheGround(int timeToDigHole) { //Set the garbage collection interval this.GarbageCollectionInterval = new TimeSpan(0, 0, timeToDigHole); //Requires us to call ReturnObject once an object has finished it's task this.AutoReturnObjects = false; } protected override object CreateObject() { //Create a man who takes 5 secs to dig a hole return new ManWhoDigsHoles(new TimeSpan(0,0,10)); } ///
/// Returns true if an object is available for work ///
///
///
protected override bool IsAvailable(object obj) { //Cast man to required type ManWhoDigsHoles man = obj as ManWhoDigsHoles; if ( man != null ) { //Return true if man has finished digging hole return (man.BusyDiggingAHole == false); } //Invalid type return false; } protected override void DisposeObject(object obj) { //Cast man to required type ManWhoDigsHoles man = obj as ManWhoDigsHoles; if ( man != null ) { //Stop man diggging hole man.StopDiggingHole(); man.Disposed(); } } ///
/// Public interface to use object pool ///
public void DigAHole() { ManWhoDigsHoles man = (ManWhoDigsHoles)this.GetObject(); man.StartDiggingHole(); this.ReturnObject(man); } } ///
/// Demo class containing a "worker" ///
public class ManWhoDigsHoles { private DateTime _startedDigging; private TimeSpan _timeToDigHole; private bool _stopped = false; private int _Id = 0; private static volatile int _nextId = 0; public ManWhoDigsHoles(TimeSpan timeToDigHole) { //Get unique Id _nextId++; _Id = _nextId; //Store the time required to dig the hole _timeToDigHole = timeToDigHole; } ///
/// Starts the man digging a hole ///
public void StartDiggingHole() { Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". Started from digging hole. "); _startedDigging = DateTime.Now; Thread.Sleep(_timeToDigHole); Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". Finished digging hole. "); } ///
/// Tests to see if the man is still digging a hole ///
public bool BusyDiggingAHole { get { if(_startedDigging.Add(_timeToDigHole) < DateTime.Now && _stopped == false) { //Still digging hole Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". BusyDiggingAHole returned false... "); return false; } //Finished digging hole Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". BusyDiggingAHole returned true... "); return true; } } ///
/// Stops the man digging a hole ///
public void StopDiggingHole() { if ( _stopped == false ) { Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". Stopped from digging hole. "); _stopped = true; } } public void Disposed() { Console.WriteLine(DateTime.Now.ToString("hh:mm:ss") + ". Id = " + _Id.ToString() + ". Thread = " + System.AppDomain.GetCurrentThreadId().ToString() + ". Disposed. "); } ///
/// The main entry point for the test application. ///
[STAThread] static void Main(string[] args) { int timeToDigHole = 3; MenWhoDigHolesInTheGround workers = new MenWhoDigHolesInTheGround(timeToDigHole); Thread[] threads = {new Thread(new ThreadStart(workers.DigAHole)), new Thread(new ThreadStart(workers.DigAHole)), new Thread(new ThreadStart(workers.DigAHole)), new Thread(new ThreadStart(workers.DigAHole)) }; foreach(Thread th in threads) { th.Start(); Thread.Sleep(timeToDigHole * 2); } Console.ReadLine(); } } } */
Terms and Conditions
Support this site
Download a trial version of the best FTP application on the internet