The need could arise where one has client threads which need to access individual objects which may not be inherently thread safe. If the object or data has not locked its internal processing, how is one to assure thread safety when two threads have the same object? The short answer is a Semaphore class which is used to limit a particular resource. We will demonstrate the LockEncapsulator class which uses a Semaphore and encapsulates the a target data/class which is the target resource for threads.

Order Example

Say for example you have a list of orders. Each of the order objects were written without threading in mind. You now have an application which does multithreading and could conceivable access the same order object in multiple threads.

Here is a non thread safe class below that will simulate our needs. It simply takes in an order number and prints out that ID with who requested it. Note, I don’t recommend writing to the console, or doing any GUI operation on separate threads; it is only shown here for example purposes.

public class Order
{
    public Order( int orderNumber )
    {
        OrderNumber = orderNumber;
    }

    public int OrderNumber { get; set; }

    public void PrintOrder( string threadID )
    {
        Console.WriteLine( "Order {0} accessed by {1}", OrderNumber, threadID );
    }

}

LockEncapsulator

We will create a class which can encapsulate the order class, or any other and provide a methodology of locking that object, using a semaphore, and releasing it to be used by other threads.

// Holds an object and uses a semephore to guarantee that
   // the object is only accessed one and only one time.
   public class LockEncapsulator<T>
   {
       private readonly T targetObject;
       private Semaphore semLock;

       public LockEncapsulator(T target)
       {
           targetObject = target;

           // 1 object, for only 1 operation.
           semLock = new Semaphore( 1, 1 );

       }

       // Called by the consumer to start.
       public T InstanceGet()
       {
           semLock.WaitOne();
           return targetObject;
       }

       // Called by the consumer when done.
       public void InstanceReturn()
       {
           semLock.Release();
       }

   }

The thread will access the target object by calling InstanceGet. When that is called, if no other thread has it locked, it will immediately return with, in our case, the order instance. Otherwise if it is being used, then the thread will wait until the object is released. When the operations are done, the thread must call InstanceReturn to allow other threads to use it. That due diligence will allow us to lock our resource, Order, and not have to worry about individual locking of code sections.

The following code demonstrates how that locking using the LockEncapsulator can be done. It will create two orders, saving them to a dictionary. Then it will create two threads, Thing1 and Thing2. Each thread will receive order 1001. Thing1 will take its time and wait 5 seconds before returning the object and calling InstanceReturn.

public class OrderManager
{
    // First int is the Order number
    public Dictionary<int, LockEncapsulator<Order>> Storage { get; set; }


    public static void Usage()
    {
        Thread Thing1, Thing2;

        OrderManager OM = new OrderManager();

        OM.Storage = new Dictionary<int, LockEncapsulator<Order>>();

        OM.Storage.Add( 1001, new LockEncapsulator<Order>( new Order( 1001 ) ) );
        OM.Storage.Add( 1002, new LockEncapsulator<Order>( new Order( 1002 ) ) );

        Thing1 = new Thread( Thing1Work );
        Thing2 = new Thread( Thing2Work );

        Thing1.Start( OM.Storage[1001] );
        Thing2.Start( OM.Storage[1001] );

        Thing2.Join();


    }

    static void Thing1Work( object obj )
    {
        LockEncapsulator<Order> le = obj as LockEncapsulator<Order>;
        if (le != null)
        {
            Order or = le.InstanceGet();

            or.PrintOrder( "Thing 1" );

            Thread.Sleep( 5000 );

            le.InstanceReturn();

        }

    }

    static void Thing2Work( object obj )
    {

        LockEncapsulator<Order> le = obj as LockEncapsulator<Order>;
        if (le != null)
        {
            Order or = le.InstanceGet();

            or.PrintOrder( "Thing 2" );

            le.InstanceReturn();

        }
    }
}

To use this and test this OrderManger class, simply call the static Usage() and it will be done. The following is outputted by the threads:

Order 1001 accessed by Thing 1
Order 1001 accessed by Thing 2

Notes

  • Even if Thing2 gets activated by the system before Thing1…no harm at all.
  • Each Thread must call InstanceReturn or Deadlocks will occur. That is the downside…
Share

Tags: