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.<code>
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) =>
{
}, 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);
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!