Click here to Skip to main content
Licence CPOL
First Posted 15 Mar 2011
Views 4,093
Bookmarked 1 time

Wrapping a not thread safe object

By | 15 Mar 2011 | Article
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.

    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 :

    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:

 public void Double()  

 Will be wrapped with:

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

 The implementation of EndDouble is pretty straight forward

 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.

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.

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.

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. 

((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. 

 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.

 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. 

[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!

[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)

About the Author

Slomka Elia

Team Leader
Elbit Security Systems
Israel Israel

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 1 PinmemberPaulo Zemek2:36 15 Mar '11  
GeneralRe: My vote of 1 PinmemberSlomka Elia4:30 15 Mar '11  
GeneralRe: My vote of 1 [modified] PinmemberPaulo Zemek4:33 15 Mar '11  
GeneralRe: My vote of 1 [modified] PinmemberSlomka Elia4:51 15 Mar '11  
GeneralRe: My vote of 1 PinmemberPaulo Zemek5:49 15 Mar '11  
GeneralRe: My vote of 1 PinmemberSlomka Elia6:35 15 Mar '11  
GeneralRe: My vote of 1 PinmemberPaulo Zemek7:58 15 Mar '11  
GeneralMy vote of 1 PinmemberAkram Gassoub2:31 15 Mar '11  

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

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 15 Mar 2011
Article Copyright 2011 by Slomka Elia
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid