Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non blocking locking

I want to start some new threads each for one repeating operation. But when such an operation is already in progress, I want to discard the current task. In my scenario I need very current data only - dropped data is not an issue.

In the MSDN I found the Mutex class but as I understand it, it waits for its turn, blocking the current thread. Also I want to ask you: Does something exist in the .NET framework already, that does the following:

  1. Is some method M already being executed?
  2. If so, return (and let me increase some counter for statistics)
  3. If not, start method M in a new thread
like image 348
DerMike Avatar asked Feb 09 '12 10:02

DerMike


1 Answers

The lock(someObject) statement, which you may have come across, is syntactic sugar around Monitor.Enter and Monitor.Exit.

However, if you use the monitor in this more verbose way, you can also use Monitor.TryEnter which allows you to check if you'll be able to get the lock - hence checking if someone else already has it and is executing code.

So instead of this:

var lockObject = new object(); 

lock(lockObject)
{
    // do some stuff
}

try this (option 1):

int _alreadyBeingExecutedCounter;
var lockObject = new object();

if (Monitor.TryEnter(lockObject))
{
   // you'll only end up here if you got the lock when you tried to get it - otherwise you'll never execute this code.

    // do some stuff

    //call exit to release the lock
    Monitor.Exit(lockObject);
}
else
{
    // didn't get the lock - someone else was executing the code above - so I don't need to do any work!
   Interlocked.Increment(ref _alreadyBeingExecutedCounter);
}

(you'll probably want to put a try..finally in there to ensure the lock is released)

or dispense with the explicit lock althogether and do this

(option 2)

private int _inUseCount;

public void MyMethod()
{
    if (Interlocked.Increment(ref _inUseCount) == 1)
    {
        // do dome stuff    
    }
    Interlocked.Decrement(ref _inUseCount);
}

[Edit: in response to your question about this]

No - don't use this to lock on. Create a privately scoped object to act as your lock.

Otherwise you have this potential problem:

public class MyClassWithLockInside
{
    public void MethodThatTakesLock()
    {
        lock(this)
        {
            // do some work
        }
    }
 }

public class Consumer
{
    private static MyClassWithLockInside _instance = new MyClassWithLockInside();

    public void ThreadACallsThis()
    {
          lock(_instance)
          {
              // Having taken a lock on our instance of MyClassWithLockInside,
              // do something long running
              Thread.Sleep(6000);
           }
    }

    public void ThreadBCallsThis()
    {
         // If thread B calls this while thread A is still inside the lock above,
         // this method will block as it tries to get a lock on the same object
         // ["this" inside the class = _instance outside]
         _instance.MethodThatTakesLock();
    }  
}

In the above example, some external code has managed to disrupt the internal locking of our class just by taking out a lock on something that was externally accessible.

Much better to create a private object that you control, and that no-one outside your class has access to, to avoid these sort of problems; this includes not using this or the type itself typeof(MyClassWithLockInside) for locking.

like image 98
Rob Levine Avatar answered Sep 26 '22 00:09

Rob Levine