views:

1988

answers:

13

I've got a bunch of properties which I am going to use read/write locks on. I can implement them either with a try finally or a using clause. In the try finally I would acquire the lock before the try, and ralease in the finally. In the using clause, I would create a class which acquires the lock in its constructor, and releases in its Dispose method. I'm using read/write locks in a lot of places so I've been looking for ways that might be more concise than try finally. I'm interested in hearing some ideas on why one way may not be recommended, or why one might be better than another.

Method 1 (try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get 
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set 
    { 
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

Method 2:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}
public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}
+9  A: 

I definitely prefer the second method. It is more concise at the point of usage, and less error prone.

In the first case someone editing the code has to be careful not to insert anything between the Acquire(Read|Write)Lock call and the try.

(Using a read/write lock on individual properties like this is usually overkill though. They are best applied at a much higher level. A simple lock will often suffice here since the possibility of contention is presumably very small given the time the lock is held for, and acquiring a read/write lock is a more expensive operation than a simple lock).

Rob Walker
How about being fail-safe? I know a try finally will always fire the finally block, is there any way the dispose won't get called?
Jeremy
No, the using statement is essentially syntactic sugar for the try/finally pattern.
Blair Conrad
The using model ensures that the object will always be disposed.
Nathen Silver
If you use reflector you will see that the using block is translated by the compiler into a try ... finally construct, so at the IL level they are equivalent. The 'using' is just syntatic sugar.
Rob Walker
^^ remember there are potential scenarios where finally is not called. A power cut for example. ;)
Quibblesome
A: 

I think method 2 would be better.

  • Simpler and more readable code in your properties.
  • Less error-prone since the locking code doesn't have to be re-written several times.
Nathen Silver
+1  A: 

DRY says: second solution. The first solution duplicates the logic of using a lock, whereas the second does not.

Tetha
+15  A: 

From MSDN, using Statement (C# Reference)

The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object. You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler. The code example earlier expands to the following code at compile time (note the extra curly braces to create the limited scope for the object):

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

So basically, it is the same code but with a nice automatic null-checks and an extra scope for your variable. The documentation also states that it "ensures the correct use of IDisposable object" so you might as well gets even better framework support for any obscure cases in the future.

So go with option 2.

Having the variable inside a scope that ends immediately after it's no longer needed is also a plus.

chakrit
A: 

Try/Catch blocks are generally for exception handling, while using blocks are used to ensure that the object is disposed.

For the read/write lock a try/catch might be the most useful, but you could also use both, like so:

using (obj)
{
  try { }
  catch { }
}

so that you can implicitly call your IDisposable interface as well as make exception handling concise.

Luke
+2  A: 

I like the 3rd option

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

public DateTime MyDateTime{
  get{
    lock(_myDateTimeLock){return _myDateTime;}
  }
  set{
    lock(_myDateTimeLock){_myDateTime = value;}
  }
}

Of your two options, the second option is the cleanest and easier to understand what's going on.

Will
lock statement does not work the same way ReaderWriterLock does.
chakrit
@chakrit: No, but unless you know there actually is lock contention they're likely to be more performant.
Michael Burr
True, but you should always go for the smiple lock first. The subby's question doesn't state anything about performance issues or requirements that won't allow a lock. Therefore there's no reason to try and be slick. Just lock it and rock it.
Will
+2  A: 

consider the possibility that both solutions are bad because they mask exceptions

a try without a catch should obviously be a bad idea; see msdn for why the using statement is likewise dangerous

note also the MS now recommends ReaderWriterLockSlim instead of ReaderWriterLock

finally, note that the MS examples use two try-catch blocks to avoid these issues, e.g.

try
{
    try
    {
         //readerwriterlock stuff
    }
    finally
    {
         //release lock
    }
 }
 catch(Exception ex)
 {
    //do something with exception
 }

a simple, consistent, clean solution is a good goal, but assuming you can't just use lock(this){return mydateetc;} you might reconsider the approach; with more info i'm sure SO can help ;-)

Steven A. Lowe
A try finally does not necessarily mask the exception. In my example, the lock is aquired, then if any exception is thrown within the scope, the lock will be freed, but the exception will still bubble up.
Jeremy
@Jeremy: if your finally block throws an exception it will mask the exception thrown in your try block - that's what the msdn article was saying was the (same) problem with the using syntax
Steven A. Lowe
+2  A: 

I personally use the C# "using" statement as often as possible, but there are a few specific things that I do along with it to avoid the potential issues mentioned. To illustrate:

void doSomething()
{
    using (CustomResource aResource = new CustomResource())
    {
        using (CustomThingy aThingy = new CustomThingy(aResource))
        {
            doSomething(aThingy);
        }
    }
}

void doSomething(CustomThingy theThingy)
{
    try
    {
        // play with theThingy, which might result in exceptions
    }
    catch (SomeException aException)
    {
        // resolve aException somehow
    }
}

Note that I separate the "using" statement into one method and the use of the object(s) into another method with a "try"/"catch" block. I may nest several "using" statements like this for related objects (I sometimes go three or four deep in my production code).

In my Dispose() methods for these custom IDisposable classes, I catch exceptions (but NOT errors) and log them (using Log4net). I have never encountered a situation where any of those exceptions could possibly affect my processing. The potential errors, as usual, are allowed to propagate up the call stack and typically terminate processing with an appropriate message (the error and stack trace) logged.

If I somehow encountered a situation where a significant exception could occur during Dispose(), I would redesign for that situation. Frankly, I doubt that will ever happen.

Meanwhile, the scope and cleanup advantages of "using" make it one of my most favorite C# features. By the way, I work in Java, C#, and Python as my primary languages, with lots of others thrown in here and there, and "using" is one of my most favorite language features all around because it is a practical, everyday workhorse.

Rob Williams
+1  A: 

"Bunch of properties" and locking at the property getter and setter level looks wrong. Your locking is much too fine-grained. In most typical object usage, you'd want to make sure that you acquired a lock to access more than one property at the same time. Your specific case might be different but I kinda doubt it.

Anyway, acquiring the lock when you access the object instead of the property will significantly cut down on the amount of locking code you'll have to write.

Hans Passant
Yes, I defintely see your point. Most of my properties are actually bools, ints, which I woudln't need to lock because they should be atomic. There are some datetime, string ones that I'd want the lock on. because the minority would need locks, I thight best to put it on the property
Jeremy
A: 

While I agree with many of the above comments, including the granularity of the lock and questionable exception handling, the question is one of approach. Let me give you one big reason why I prefer using over the try {} finally model... abstraction.

I have a model very similar to yours with one exception. I defined a base interface ILock and in it I provided one method called Acquire(). The Acquire() method returned the IDisposable object and as a result means that as long as the object I am dealing with is of type ILock that it can be used to do a locking scope. Why is this important?

We deal with many different locking mechanisms and behaviors. Your lock object may have a specific timeout that employs. Your lock implementation may be a monitor lock, reader lock, writer lock or spin lock. However, from the perspective of the caller all of that is irrelevant, what they care about is that the contract to lock the resource is honored and that the lock does it in a manner consistent with it's implementation.

interface ILock {
    IDisposable Acquire();
}

class MonitorLock : ILock {
    IDisposable Acquire() { ... acquire the lock for real ... }
}

I like your model, but I'd consider hiding the lock mechanics from the caller. FWIW, I've measured the overhead of the using technique versus the try-finally and the overhead of allocating the disposable object will have between a 2-3% performance overhead.

Ajaxx
A: 

I'm surprised no one has suggested encapsulating the try-finally in anonymous functions. Just like the technique of instantiating and disposing of classes with the using statement, this keeps the locking in one place. I prefer this myself only because I'd rather read the word "finally" than the word "Dispose" when I'm thinking about releasing a lock.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                rwlMyLock_m,
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                rwlMyLock_m,
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private static DateTime ReadLockedMethod(
        ReaderWriterLock rwl,
        ReadLockMethod method
    )
    {
        rwl.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }

    private static void WriteLockedMethod(
        ReaderWriterLock rwl,
        WriteLockMethod method
    )
    {
        rwl.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }
}
A: 

Silly me. There's a way to make that even simpler by making the locked methods part of each instance (instead of static like in my previous post). Now I really prefer this because there's no need to pass `rwlMyLock_m' off to some other class or method.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private DateTime ReadLockedMethod(ReadLockMethod method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(WriteLockMethod method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}
Just edit your other answer, don't duplicate it.
TheSoftwareJedi
A: 

SoftwareJedi, I don't have an account, so I can't edit my answers.

In any case, the previous version wasn't really good for general purpose use since the read lock always required a return value. This fixes that:

class StackOTest
{
    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            DateTime retval = default(DateTime);
            ReadLockedMethod(
                delegate () { retval = dtMyDateTime_m; }
            );
            return retval;
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private void ReadLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}