I need to make an existing app thread safe. Due circumstances (see below), I decided to use one single ReaderWriterLock for the entire graph of business objects. All methods/properties must look like these:
public int MyReadOperation(string inputParam)
{
rwLock.AcquireReaderLock(10000);
try
{
// do all read operations
...
}
finally
{
rwLock.ReleaseReaderLock();
}
}
public void MyWriteOperation(string input)
{
rwLock.AcquireWriterLock(10000);
try
{
// do all write operations
...
}
finally
{
rwLock.ReleaseWriterLock();
}
}
But I have immense amount of methods to cover and I am freaked out from the idea of copy/pasting. Inspired by MethodImplAttribute, I would prefer to have a code like this while behave as the code above:
[ReadOperation]
public int MyReadOperation(string inputParam)
{
// do all read operations
...
}
[WriteOperation]
public void MyWriteOperation(string input)
{
// do all write operations
...
}
Is there a way to interrupt Thread execution before/after entering into a property or a method and adding the thread-safety precautions? Or somehow utilizing the functional language features of C#, embedding the productive body of the methods into a generic ReaderWriterLock aquiring "frame"?
A bit of background:
I am working on a project where data carrier business objects are exposed via .NET Remoting. However, these data classes are not serializable but MarshalByRef-s. That means that ALL clients actually read/write the very same business objects. This cannot be changed, it is carved in stone. The hope for thread-safety is that these remoted business objects are read-only in the eyes of the remoting clients (thought they do loop many lists) and all write operations are nicely separated into a dedicated facade. I expect rare writes and frequent reads. The business objects are highly connected, they are very "graphy".
First of all, thanx to Andrew pointing me to PostSharp. Based on his answer, I ended up with this final one.
[Serializable]
public class ReadOperationAttribute : PostSharp.Laos.OnMethodInvocationAspect
{
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
{
ReaderWriterLock rwLock = GetLock();
rwLock.AcquireReaderLock(10000);
try { eventArgs.Proceed(); }
finally { rwLock.ReleaseReaderLock();}
}
}
public class Foo
{
[ReadOperation]
public string Bar
{
get { return "stuff"; }
set { Console.WriteLine(value); }
}
[ReadOperation]
public void Test(string input)
{
Console.WriteLine(input);
}
}
It does exactly what I expressed in the question and is perfectly debuggable. We can make it even more useful, if we declare the attribute for an entire assembly:
[assembly: ReadOperation(AttributeTargetElements=MulticastTargets.Method,
AttributeTargetTypes="MyNamespace.*")]
This way we do not have to decorate every method/property, but placing this attribute in the assembly once, we braket all our methods/properties with ReaderWriterLock.
Also, as it has been pointed out by Andrew, Microsoft recommends to use ReaderWriterLockSlim if you use .NET3.5 or above.