Search code examples
.netmultithreadingfunctional-programmingremotingdeclarative

Declarative thread safety in .NET


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


Solution

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