Click here to Skip to main content
15,896,557 members
Articles / Programming Languages / C# 3.5

Wrapping a not thread safe object

Rate me:
Please Sign up or sign in to vote.
1.00/5 (2 votes)
15 Mar 2011CPOL3 min read 16.3K   3   8
A standardized wrapper for thread unsafe object implementing Async pattern

Introduction

A colleague came to me with a question the answer for seemed very straight forward, yet an implementation took us more than a few minutes. The question was: I have a legacy object which is not thread safe, and all its methods, by its nature, synchronous. It was originally written to run on GUI thread in client application. Later on, this object had to be moved to server where it has to face new challenges. Now the requests to use this method come from the network, means, we face thread safety problem, we have to make sure only one method runs simultaneously on this object. On the other hand methods are synchronous and it would be a bad practice to keep our network "waiting". This article shows how to create a simple wrapper around the object which will synchronize the calls to original object and expose standard async methods.

An unsafe object to wrap

First lets create an unsafe class we will later on wrap, this is a test class that will indicate a proper or improper use. It can be replaced with a real unsafe object.

C#
public class UnsafeClass
{
    long testValue = 0;
    int myInt = 5;

    public void Double()
    {
        if (Interlocked.CompareExchange(ref testValue, 1, 0) != 0)
            throw new ApplicationException("More then one method of UnsafeClass called simultaneously");

        myInt = myInt * 2;
        Thread.Sleep(100);

        Interlocked.Decrement(ref testValue);
    }
}

You may see we use Interlocked on a private class variable to make sure the method is running alone. And a Thread.Sleep to simulate a real action.

A wrapper

So what do we really need. An object to lock on. An instance of out UnsafeClass. And a Queue of AutoResetEvent objects (Queue<AutoResetEvent>).

The class will look like this :

C#
public class SafeClassWrapper
{
    UnsafeClass unsafeClass;
    Queue<AutoResetEvent> syncQueue;
    object syncRoot = new object();

    public SafeClassWrapper()
    {
        unsafeClass = new UnsafeClass();
        syncQueue = new Queue<AutoResetEvent>();
    }
}

 Now its time to wrap out methods with an Aync pattern. For example a mehod:

C#
public void Double()

 Will be wrapped with:

C#
public IAsyncResult BeginDouble(AsyncCallback callback, object state)
public void EndDouble(IAsyncResult result)

 The implementation of EndDouble is pretty straight forward

C#
AsyncResultNoResult ar = ((AsyncResultNoResult)result);
ar.EndInvoke();

I believe it is time to say I use the implementations of IAsyncResult taken from this article http://msdn.microsoft.com/en-us/magazine/cc163467.aspx .

 Now, it is the BeginDouble that does the magic. First of all we need to create an IAsyncResult to return it.<code>

C#
AsyncResultNoResult asyncResult = new AsyncResultNoResult(callback, state); 

Now we need a to create an AutoResetEvent object, ant put it in our queue. Also we need to check if there is nothing in queue to enable the first item to run.

C#
AutoResetEvent autoResetEvent;
lock (syncRoot)
{
    if (syncQueue.Count != 0)
        autoResetEvent = new AutoResetEvent(false);
    else
        autoResetEvent = new AutoResetEvent(true);

    syncQueue.Enqueue(autoResetEvent);
} 

As you can see, we take a lock on our syncRoot, check the state of our queue and enqueue the item, if the queue was empty the item will be set as ready to run.

Now lets start our action asynchronously using ThreadPool and return the IAsyncResult object.

C#
ThreadPool.QueueUserWorkItem((a) =>
 {  
     //CODE
 }, new object[] { asyncResult, autoResetEvent });
return asyncResult; 

 Inside the thread we need to wait for our turn to run, doing this by extracting the AutoResetEvent object from the state we passed to the thread. 

C#
((AutoResetEvent)((object[])a)[1]).WaitOne(); 

 When we got a green light run the action inside Try...Catch to make sure we don't get an UnhadledThreadException and signal completion. 

C#
 try
 {
     unsafeClass.Double();
     ((AsyncResultNoResult)((object[])a)[0]).SetAsCompleted(null, false);
 }
 catch (Exception ex)
 {
     ((AsyncResultNoResult)((object[])a)[0]).SetAsCompleted(ex, false);
 } 

  And the last piece of magic, run the next command if it is present in queue.

C#
 AutoResetEvent autoResetEventInThread = null;
 lock (syncRoot)
 {
     if (syncQueue.Count != 0)
         autoResetEventInThread = syncQueue.Dequeue();
 }
 if (autoResetEventInThread != null)
     autoResetEventInThread.Set(); 

 Thats it. We now have a safe and true asynchronous wrapper.

 Testing 

 First, lets make sure our UnsafeClass will really fail if running on multiple threads simultaneously. 

C#
[TestMethod()]
public void MultiThreadOnUnsafeClassSingleMethodTest()
{
    UnsafeClass target = new UnsafeClass();
    bool? expectedException = null;

    for (int i = 0; i < 50; i++)
    {
        ThreadPool.QueueUserWorkItem((a) =>
        {
            try
            {
                target.Double();                        
            }
            catch (Exception ex)
            {
                if (!expectedException.HasValue ||
                    (expectedException.HasValue && expectedException.Value))
                    expectedException = (ex is ApplicationException);
            }
        });
    }

    Thread.Sleep(60 * 100);
    Assert.IsTrue(expectedException.HasValue, "Exception was not thrown");
    Assert.IsTrue(expectedException.Value, "Exception thrown was not an ApplicationException");
} 

 Now lets test our wrapper!

C#
[TestMethod()]
public void MultiThreadOnSafeWrapperSingleMethodTest()
{
    SafeClassWrapper target = new SafeClassWrapper();
    bool? expectedException = null;

    for (int i = 0; i < 50; i++)
    {
        ThreadPool.QueueUserWorkItem((a) =>
        {
            target.BeginDouble(new AsyncCallback((ar) =>
            {
                if (i % 3 == 0)
                    Thread.Sleep(50);//Just to add some random
                try
                {
                    target.EndDouble(ar);
                }
                catch (Exception ex)
                {
                    if (!expectedException.HasValue ||
                        (expectedException.HasValue && expectedException.Value))
                        expectedException = (ex is ApplicationException);
                }
            })
            , null);
        });
    }

    Thread.Sleep(70 * 100);

    Assert.IsFalse(expectedException.HasValue);
} 

 What else?... 

 In the solution attached you can find the implementation of all the above including test project. Also, there is a sample of a method with return value. 

Comments are welcome. 

Happy wrapping! 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Elbit Security Systems
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 1 Pin
Paulo Zemek15-Mar-11 2:36
Paulo Zemek15-Mar-11 2:36 
GeneralRe: My vote of 1 Pin
Slomka Elia15-Mar-11 4:30
Slomka Elia15-Mar-11 4:30 
GeneralRe: My vote of 1 [modified] Pin
Paulo Zemek15-Mar-11 4:33
Paulo Zemek15-Mar-11 4:33 
GeneralRe: My vote of 1 [modified] Pin
Slomka Elia15-Mar-11 4:51
Slomka Elia15-Mar-11 4:51 
GeneralRe: My vote of 1 Pin
Paulo Zemek15-Mar-11 5:49
Paulo Zemek15-Mar-11 5:49 
GeneralRe: My vote of 1 Pin
Slomka Elia15-Mar-11 6:35
Slomka Elia15-Mar-11 6:35 
GeneralRe: My vote of 1 Pin
Paulo Zemek15-Mar-11 7:58
Paulo Zemek15-Mar-11 7:58 
GeneralMy vote of 1 Pin
Ak_Dyl15-Mar-11 2:31
Ak_Dyl15-Mar-11 2:31 
x

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.