HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Exclusive access to objects

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
exclusiveaccessobjects

Problem

Take a look at following code:

/*abstract*/ class Capturable
{
    object _lock = new object();
    public bool IsCaptured { get; private set; }

    public bool TryCapture()
    {
        if (!IsCaptured)
            lock (_lock)
                if (!IsCaptured)
                    return IsCaptured = true;
        return false;
    }

    public void Release()
    {
        lock (_lock)
            IsCaptured = false;
    }
}


The idea is to inherit from Capturable when I need an object with exclusive access. Object will be used from different places (not necessarily different threads). If this object is needed, then the caller should first call TryCapture() (so theoretically there could be many calls when object is occupied, therefore double check locking is used to quickly return false).

Expected scenarios of usage:

  • periodically do something (e.g. by timer);



  • get exclusive access for a duration (e.g. if there is a long operation during which nobody should change state of object);



Both cases are covered with something like:

var obj = Manager.Get();
...
if(obj.TryCapture())
    try
    {
        ...
        obj.SomeMethod();
        ...
    }
    finally
    {
        obj.Release();
    }


The questions:

-
Is this a good approach to get exclusive access? Code-wise and architecturally? E.g. I can use Monitor.TryEnter instead of double check locking, but not sure how to return IsCaptured then.

-
Is there a possibility to avoid if+TryCapture+try+finally everywhere? Similar to how clean lock looks like (it covers Monitor calls) or how nice using is to ensure Dispose() call. The problem here comes when multiple of such object has to be captured at the beginning of method, then it will looks like dozens of nested if/try/finally, which I don't like.

Solution

What happens if this is called by something that hasn't captured the object?

public void Release()
{
    lock (_lock)
        IsCaptured = false;
}


Honestly, I don't see what this is giving you that you don't get from:

Monitor.TryEnter(lockObj)


and

Monitor.Exit(lockObj);


Unless you want to release the capture from a different thread, in which case it seems like you're more likely to run into the 'Released by wrong thing` problem.

Why do you need to know if it's captured? Try to capture the lock and if it fails, you know that it's captured... What purpose does the property really serve?


The problem here comes when multiple of such object has to be captured at the beginning of method, then it will looks like dozens of nested if/try/finally

If you're going to be capturing groups of objects then you're going to need to be very careful about the order that you capture those objects in order to prevent deadlocks where one method performs:

Capture A, B, C


And another method performs

Capture C, A


One of the things you've indicated is that you don't like the idea of the nested try/finally blocks to ensure that all captured items are released. You could consider writing some utility functions to help make this cleaner. Something like this:

static bool TryCaptureAll(params Capturable[] capturables)
{
    List capturedItems = new List();

    foreach(var capturable in capturables)
    {
        if(!capturable.TryCapture())
        {
            // Failed to capture one of the items, release the ones
            // we did capture and return failure.
            foreach(var captured in capturedItems)
            {
                captured.Release();
            }
            return false;
        }

        capturedItems.Add(capturable);
    }

    return true;
}
static void ReleaseAll(params Capturable[] capturables)
{
    foreach (var capturable in capturables)
    {
        capturable.Release();
    }
}


Would allow you to lock and release groups of objects like this:

if (CaptureList.TryCaptureAll(a, b, c))
{
    try
    {
        a.SomeMethod();
        b.SomeMethod();
        c.SomeMethod();
    }
    finally
    {
        CaptureList.ReleaseAll(a, b, c);
    }
}

Code Snippets

public void Release()
{
    lock (_lock)
        IsCaptured = false;
}
Monitor.TryEnter(lockObj)
Monitor.Exit(lockObj);
Capture A, B, C
Capture C, A

Context

StackExchange Code Review Q#132179, answer score: 6

Revisions (0)

No revisions yet.