End-to-End Examples

 < Day Day Up > 



In this section of the chapter we will take a look at two larger examples. First, we'll take a look at creating thread-safe wrappers and then move on to a database connection pool.

Writing Your Own Thread-Safe Wrappers

The general idea of writing our own wrapper comes from the fact that you may not want to make every class in our library thread-safe, as synchronization has performance penalties associated with it. You would like to give the application developer a choice of whether to use a synchronized class or not. As the application developer would neither like to take the risk of a deadlock nor want to pay the performance penalty of using a thread-safe class in a single-threaded environment, they might prefer to have a choice of having a built-in synchronized wrapper for the same class in the library rather than writing a specific one. Collection classes like ArrayList and Hashtable in the System.Collections namespace already have this feature. You can decide whether you want to use a thread-safe Hashtable or not during initialization of the Hashtable. You can initialize a thread-safe Hashtable by calling the shared Synchronized() method of the Hashtable class as shown below:

    Hashtable h;    h = Hashtable.Synchronized(new Hashtable()); 

It would be good to give the application developer such a choice. In this example, we will attempt to develop a class and a synchronized wrapper for the class. We will develop a Book Collection library and Figure 4 shows the UML representation of the Book Collection library.

click to expand
Figure 4

The program is very simple, but the concept of having intrinsic synchronization support is very important. By adding intrinsic synchronization support to our library, we will allow the developer to choose between a synchronized and non-synchronized environment for the same class. For example, programmers who do not need synchronization can instantiate an object as follows:

    BookLib b = new BookLib() 

while programmers who use our type in a thread-hot environment can use the thread-safe wrappers as follows:

    BookLib b = new BookLib()    b = b.Synchronized() 

The following is the complete BookLib.cs source along with its synchronized wrapper:

    using System;    using System.Threading;    using System.Collections;    interface IBookCollection    {      void Clear();      void Add(Book n);      Book GetBook(string ISBN);      bool IsSynchronized { get; }      object SyncRoot { get; }    }    public class Book    {      public string Name;      public string ISBN;      public string Author;      public string Publisher;    }    class BookLib : IBookCollection    {      internal Hashtable bk = new Hashtable(10);      public virtual void Clear()      {        this.bk.Clear();      }      public virtual void Add(Book b)      {        Console.WriteLine("Adding Book for ThreadID:" +                          Thread.CurrentThread.GetHashCode());        Thread.Sleep(2000);        bk.Add(b.ISBN, b);      }      public virtual Book GetBook(string ISBN)      {        Console.WriteLine("Getting Book for ThreadID:" +                          Thread.CurrentThread.GetHashCode());        return (Book)bk[ISBN];      }      public virtual bool IsSynchronized      {         get { return(false); }      }      public virtual object SyncRoot      {        get { return(this); }      }      public BookLib Synchronized()      {        return Synchronized(this);      }      public static BookLib Synchronized(BookLib bc)      {        if (bc == null)        {          throw new ArgumentNullException("bc");        }        if (bc.GetType() == typeof(SyncBookLib))        {          throw new InvalidOperationException(              "BookLib reference is already synchronized.");        }        return new SyncBookLib(bc);      }      public static IBookCollection Synchronized(IBookCollection acc)      {        if (acc == null)        {          throw new ArgumentNullException("acc");        }        if (acc.GetType() == typeof(SyncBookLib))        {          throw new InvalidOperationException(              "BookLib reference is already synchronized.");        }        return new SyncBookLib(acc);      }    }    sealed class SyncBookLib : BookLib    {      private object syncRoot;      private object booklib;      internal SyncBookLib(IBookCollection acc)      {        booklib = acc;        syncRoot = acc.SyncRoot;      }      public override void Clear()      {        lock(syncRoot)        {          base.Clear();        }      }      public override void Add(Book b)      {        lock(syncRoot)        {          base.Add(b);        }      }      public override Book GetBook(string ISBN)      {        lock(syncRoot)        {          return (Book)bk[ISBN];        }      }      public override bool IsSynchronized      {        get{ return(true); }      }      public override object SyncRoot      {        get { return(syncRoot); }      }    }    class Test    {      private static BookLib acc;      private static int n = 0;      static void Main(string[] args)      {        acc = new BookLib();        if (args.Length > 0)        {          acc = acc.Synchronized();          //OR BookLib.Synchronized(acc);        }        Thread[] threads = {new Thread(new ThreadStart(Run)),                            new Thread(new ThreadStart(Run)),                            new Thread(new ThreadStart(Run))};        foreach (Thread t in threads)        {          t.Start();        }        foreach (Thread t in threads)        {          t.Join();        }        for (int i = 0; i < n; i++)        {          Book bk = acc.GetBook(i.ToString());          if (bk != null)          {            Console.WriteLine("Book : " + bk.Name);            Console.WriteLine("ISBN : " + bk.ISBN);            Console.WriteLine("Publisher : " + bk.Publisher);            Console.WriteLine("Author : " + bk.Author);          }        }        Console.WriteLine("Total Number of books added " + n);      }      static void Run()      {        for (int i = 0; i < 2; i++)        {          Book bk = new Book();          bk.Author = "Tejaswi Redkar";          bk.Name = "A" + i;          bk.Publisher = "Wrox";          bk.ISBN = (n++).ToString();          acc.Add(bk);        }      }    } 

In the above example, we first declare an interface IBookCollection, which has the following methods and properties for handling collection of books:

  • Clear() - Method to clear the book collection

  • Add() - Method to add a book to the book collection

  • GetBook() - Method to get a book from the book collection

  • IsSynchronized() - Read-only property used to check whether the collection is synchronized or not

  • SyncRoot() - Read-only property to get the synchronized root of the collection

Next we declare a class called Book representing a book in the collection. For example, the collection might be a library or a book store, but the representation of the Book class is the same in both.

The BookLib class implements the IBookCollection interface. As a result, the BookLib class must implement all the methods and properties of the IBookCollection interface. We declare a Hashtable called bk as the collection that will contain our books. The Key of the Book object will be its ISBN number. In the Add() method, we add a Book object to the Hashtable. In the GetBook() method, we retrieve the Book object if its ISBN number is supplied.

Now we must address any synchronization issues. In the Synchronized() method, we create an object of type SyncBookLib and return a reference to it. SyncBookLib is the synchronized version of the BookLib class. SyncBookLib inherits from the BookLib class, thus inheriting all the properties and methods that the BookLib class has already implemented. The difference between SyncBookLib and BookLib class is that in the SyncBookLib class, we lock all the critical sections using monitors (using the lock keyword). For example, the Clear(), GetBook(), and Add() methods have locks in their implementations thus making them thread-safe, whereas, in the BookLib class, there are no locks in any of the methods.

In the Test class, we create a synchronized BookLib if we pass any command-line argument. If there are no command-line arguments passed, we create a non thread-safe BookLib object. Then we create three threads that add some books to our book library. When you run the application, the difference between the execution of synchronized BookLib and non-synchronized BookLib will be clear. In the synchronized version, only one thread can access the library at any point of time. So, the other two threads have to wait until the first thread has finished adding books to the BookLib. This is not the case if we use the non-synchronized version; all the threads are given concurrent access to the BookLib object instance.

The output from BookLib with a command-line argument (thread-safe) will be as follows:

    Adding Book for ThreadID:2    Adding Book for ThreadID:3    Adding Book for ThreadID:4    Adding Book for ThreadID:2    Adding Book for ThreadID:3    Adding Book for ThreadID:4    Book : A0    ISBN : 0    ISBN : Wrox    Author : Tejaswi Redkar    Book : A0    ISBN : 1    ISBN : Wrox    Author : Tejaswi Redkar    Book : A0    ISBN : 2    ISBN : Wrox    Author : Tejaswi Redkar    Book : A1    ISBN : 3    ISBN : Wrox    Author : Tejaswi Redkar    Book : A1    ISBN : 4    ISBN : Wrox    Author : Tejaswi Redkar    Book : A1    ISBN : 5    ISBN : Wrox    Author : Tejaswi Redkar    Total Number of books added 6 

The output from BookLib with no command-line argument (non-thread-safe will be as follows:

    Adding Book for ThreadID:3    Adding Book for ThreadID:4    Adding Book for ThreadID:2    Adding Book for ThreadID:3    Adding Book for ThreadID:4    Adding Book for ThreadID:2    Getting Book for ThreadID:7    Book : A0    ISBN : 0    ISBN : Wrox    Author : Tejaswi Redkar    Getting Book for ThreadID:7    Book : A0    ISBN : 1    ISBN : Wrox    Author : Tejaswi Redkar    Getting Book for ThreadID:7    Book : A0    ISBN : 2    ISBN : Wrox    Author : Tejaswi Redkar    Getting Book for ThreadID:7    Book : A1    ISBN : 3    ISBN : Wrox    Author : Tejaswi Redkar    Getting Book for ThreadID:7    Book : A1    ISBN : 4    ISBN : Wrox    Author : Tejaswi Redkar    Getting Book for ThreadID:7    Book : A1    ISBN : 5    ISBN : Wrox    Author : Tejaswi Redkar    Total Number of books added 6 

A Database Connection Pool

Object pools are very common in enterprise software development where instantiation of objects has to be controlled in order to improve the performance of the application. For example, database connections are expensive objects to be created every time we need to connect to a database. So, instead of wasting resources in instantiating the database connection for every database call, we can pool and reuse some connection objects that we have already created and thus gain a performance advantage by saving the time and resources required to create a new connection object for every database call.

Object pooling is similar to a book library. The book library maintains a pool of similar books. When the demand for that particular book increases, the library buys more, else the readers just keep on reusing the same books. In Object Pooling, first we check the pool to see whether the object has already been created and pooled, if it is pooled, we get the pooled object; else we create a new one and pool it for future use. Object pooling is extensively used in large-scale application servers like Enterprise Java Beans (EJB) Servers, MTS/COM+, and even the .NET Framework.

In this section, we will develop a database connection pool for pooling database connections. Database connections are expensive to create. In a typical web application there might be thousands of users trying to access the web site at the same time. If most of these hits need database access to serve dynamic data and we go on creating new database connection for each user, we are going to affect the application performance negatively. Creating a new object requires new memory allocation. Memory allocation reduces application performance and, as a result, the web site will either slow down considerably in delivering the dynamic content, or crash after a critical point is reached. Connection pooling maintains a pool of created objects, so the application that needs a database connection can just borrow a connection from the pool and then return it to the pool when the job is done, rather than creating a new database connection. Once data is served to one user, the connection will be returned back to the pool for future use.

Implementing the Pool

Let's start by taking a look at a UML diagram that depicts our database connection pool application. Figure 5 show the ObjectPool class and the DBConnectionSingleton class that inherits the ObjectPool class.

click to expand
Figure 5

The ObjectPool Class

Let's start our discussion of the ObjectPool class by listing it in its entirety:

    using System;    using System.Collections;    using System.Timers;    namespace WroxCS    {      public abstract class ObjectPool      {        //Last Checkout time of any object from the pool.        private long lastCheckOut;        //Hashtable of the checked-out objects        private static Hashtable locked;        //Hashtable of available objects        private static Hashtable unlocked;        //Clean-Up interval        internal static long GARBAGE_INTERVAL = 90 * 1000; // 90 seconds        static ObjectPool()        {          locked = Hashtable.Synchronized(new Hashtable());          unlocked = Hashtable.Synchronized(new Hashtable());        }        internal ObjectPool()        {           lastCheckOut = DateTime.Now.Ticks;          //Create a Time to track the expired objects for cleanup.          System.Timers.Timer aTimer = new System.Timers.Timer();          aTimer.Enabled = true;          aTimer.Interval = GARBAGE INTERVAL;          aTimer.Elapsed += new              System.Timers.ElapsedEventHandler(CollectGarbage);        }        protected abstract object Create();        protected abstract bool Validate(object o);        protected abstract void Expire(object o);        internal object GetObjectFromPool()        {          long now = DateTime.Now.Ticks;          _lastCheckOut = now;          object o = null;          lock(this)          {            try            {              foreach (DictionaryEntry myEntry in unlocked)              {                o = myEntry.Key;                if (Validate(o))                {                  unlocked.Remove(o);                  locked.Add(o, now);                  return(o);                }                else                {                  unlocked.Remove(o);                  Expire(o);                  o = null;              }            }          } catch (Exception){}            o = Create();            locked.Add(o, now);          }          return(o);        }        internal void ReturnObjectToPool(object o)        {          if (o != null)          {            lock(this)            {              locked.Remove(o);              unlocked.Add(o, DateTime.Now.Ticks);            }          }        }        private void CollectGarbage(object sender,            System.Timers.ElapsedEventArgs ea)        {          lock(this)          {            object o;            long now = DateTime.Now.Ticks;            IDictionaryEnumerator e = unlocked.GetEnumerator();            try            {              while(e.MoveNext())              {                o = e.Key;                if ((now - ((long) unlocked[ o ])) > GARBAGE INTERVAL )                {                  unlocked.Remove(o);                  Expire(o);                  o = null;                }              }            }            catch (Exception){}          }        }      }    } 

The ObjectPool base class contains two important methods; GetObjectFromPool(), which gets an object from the pool, and ReturnObjectToPool(), which returns object to the Pool. The object pool is implemented as two hashtables, one called locked and the other called unlocked. The locked hashtable contains all the objects that are currently in use and unlocked contains all the objects that are free and available for use. The ObjectPool also contains three MustOverride methods Create(), Validate(), and Expire(), that must be implemented by the derived classes.

In total, there are three critical sections in the ObjectPool class:

  • While getting an object to the pool, GetObjectFromPool() is used - A lock is needed while adding an object to the pool because the content of the locked and unlocked hashtables change and we do not want any race condition here.

  • While returning an object to the pool, ReturnObjectToPool() is used - Again, a lock is needed while returning an object to the pool because the content of the locked and unlocked hashtables will change and a new object will be available for use. Here also we cannot afford to have a race condition, because, we do not want multiple threads accessing the same hashtable at the same time.

  • While cleaning up the expired objects from the pool, CollectGarbage() - In this method, we go over the unlocked hashtable to find and remove expired objects from the pool. The content of the unlocked hashtable may change and we need the removal of expired objects to be atomic.

In the GetObjectFromPool() method, we iterate over the unlocked hashtable to get the first available object. The Validate() method is used to validate the specific object. The Validate() method may vary in specific implementations based on the type of the pooled object. For example, if the object is a database connection, the derived class of the object pool needs to implement the Validate() method to check whether the connection to the database is open or closed. If the validation of the pooled object succeeds, we remove the object from the unlocked hash table and put it in the locked hashtable. The locked hashtable contains the objects that are currently in use. If the validation fails, we kill the object with the Expire() method. The Expire() method also needs to be implemented by the derived class and is specific to the specific type of pooled object. For example, in the case of a database connection, the expired object will close the database connection. If a pooled object is not found, that is if the unlocked hashtable is empty, we create a new object using the Create() method and put the object in the locked hashtable.

The ReturnObjectToPool() method implementation is much simpler. We just have to remove the object from the locked hashtable and put it back in the unlocked hash table for recycling. In this whole recycling process, we have to take into consideration the memory usage of the application. Object pooling is directly proportional to memory usage. So, the more objects we pool, the more memory we will be using. To control memory usage, we should periodically garbage-collect the objects that are pooled. This can be achieved by assigning a timeout period to every pooled object. If the pooled object is not used within the timeout period, it will be garbage-collected. As a result, the memory usage of the object pool will vary depending on the load on the system. The CollectGarbage() method is used for handling the garbage collection of the pooled object. This method is called by the aTimer delegate that is initialized in the ObjectPool constructor. In our example, we set the garbage-collection interval to 90 seconds in the GARBAGE_COLLECT constant.

start sidebar

We haven't implemented any database connection-specific code so we can assume that the ObjectPool class can be used for the pooling any type of .NET Framework objects.

end sidebar

The DBConnectionSingleton Class

The DBConnectionSingleton class is the implementation of a database connection-specific object pool. The main purpose of this class is to provide database connection-specific implementations of the Create(), Validate(), and Expire() methods inherited from the ObjectPool class. The class also provides methods called BorrowDBConnection() and ReturnDBConnection() for borrowing and returning database connection objects from the object pool.

The complete listing of the DBConnectionSingleton class is as follows:

    using System;    using System.Data.SqlClient;    namespace WroxCS    {      public sealed class DBConnectionSingleton : ObjectPool      {        private DBConnectionSingleton() {}        public static readonly DBConnectionSingleton Instance =            new DBConnectionSingleton();        private static string _connectionString =            @"server=(local);Trusted Connection=yes;database=northwind";        public static string ConnectionString        {          set          {            _connectionString = value;          }          get          {            return connectionString;          }        }        protected override object Create()        {          SqlConnection temp = new SqlConnection( connectionString);          temp.Open();          return(temp);        }        protected override bool Validate(object o)        {          try          {            SqlConnection temp = (SqlConnection)o;           return(             ! ((temp.State.Equals(System.Data.ConnectionState.Closed))));          }          catch (SqlException)          {            return false;          }        }        protected override void Expire(object o)        {          try          {            ((SqlConnection) o ).Close();          }          catch (SqlException)          {          }        }        public SqlConnection BorrowDBConnection()        {          try          {            return((SqlConnection)base.GetObjectFromPool());          }          catch (Exception e)          {            throw e;          }        }        public void ReturnDBConnection(SqlConnection c)        {          base.ReturnObjectToPool(c);        }      }    } 

As you are dealing with the SqlConnection object, the Expire() method closes the SqlConnection, the Create() method creates the SqlConnection, and the Validate() method checks whether the SqlConnection is open or not. The whole synchronization issue is hidden from the client application using the DBConnectionSingleton object instance.

Why Use a Singleton?

The Singleton is a popular creational design pattern, which is used when you need to have only one instance of an object. The intent of the Singleton pattern as defined in Design Patterns (ISBN 0-201-70265-7) is to ensure a class has only one instance, and provide a global point of access to it. To implement a Singleton, we need a Private constructor so that the client application is not able to create a new object whenever it wants to, and the static ReadOnly property instance is used to create the only instance of the Singleton class. The .NET Framework, during the JIT process, will initialize the static property when (and only when) any method uses this static property. If the property is not used, then the instance is not created. More precisely, the class gets constructed and loaded when any static member of the class is used by any caller. This feature is called lazy initialization and gives the effect of the creation of the object only on the first call to the instance property. The .NET Framework guarantees the thread safety of shared type initializations inherently. So we do not have to worry about the thread safety of the DBConnectionSingleton object because only one instance of it will ever be created during the lifetime of the application. The static variable instance holds the only instance of the DBConnectionSingleton class object.

Using the Database Connection Pool

The database connection pool is now ready for use and the ObjectPoolTester application in the code download for this chapter can be used to test it.

Below we show some code snippets of how to instantiate and use the database connection pool:

    // Initialize the Pool    DBConnectionSingleton pool;    pool = DBConnectionSingleton.Instance    // Set the ConnectionString of the DatabaseConnectionPool    DBConnectionSingleton.ConnectionString =        "server=(local);User ID=sa;Password=;database=northwind"    // Borrow the SqlConnection object from the pool    SqlConnection myConnection = pool.BorrowDBConnection()    // Return the Connection to the pool after using it    pool.ReturnDBConnection(myConnection) 

In the above examples, we initialize the DBConnectionSingleton object from the instance property of the DBConnectionSingleton class. As discussed above, we are assured that with the use of the Singleton design pattern we have one and only one instance of the DBConnectionSingleton object. We set the ConnectionString property of the database connection to the Northwind database on the local SQL Server machine. Now, we can borrow database connections from the object pool using the BorrowDBConnection() method of the pool object, and return the database connection by calling the returnDBConnection() method of the pool object. The following screenshot shows the ObjectPoolTester application in action. If you really want to explore how the pooling application works, the best way is to open the project in Visual Studio .NET and step through the ObjectPoolTester application in Debug mode.

click to expand



 < Day Day Up > 



C# Threading Handbook
C# Threading Handbook
ISBN: 1861008295
EAN: 2147483647
Year: 2003
Pages: 74

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