Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to prevent reentrancy and ensure a lock is acquired for certain operations?

I'm designing a base class that, when inherited, will provide business functionality against a context in a multithreaded environment. Each instance may have long-running initialization operations, so I want to make the objects reusable. In order to do so, I need to be able to:

  1. Assign a context to one of these objects to allow it to do its work
  2. Prevent an object from being assigned a new context while it already has one
  3. Prevent certain members from being accessed while the object doesn't have a context

Also, each context object can be shared by many worker objects.

Is there a correct synchronization primitive that fits what I'm trying to do? This is the pattern I've come up with that best fits what I need:

private Context currentContext;

internal void BeginProcess(Context currentContext)
{
    // attempt to acquire a lock; throw if the lock is already acquired,
    // otherwise store the current context in the instance field
}

internal void EndProcess()
{
    // release the lock and set the instance field to null
}

private void ThrowIfNotProcessing()
{
    // throw if this method is called while there is no lock acquired
}

Using the above, I can protect base class properties and methods that shouldn't be accessed unless the object is currently in the processing state.

protected Context CurrentContext
{
    get
    {
        this.ThrowIfNotProcessing();
        return this.context;
    }
}

protected void SomeAction()
{
    this.ThrowIfNotProcessing();

    // do something important
}

My initial though was to use Monitor.Enter and related functions, but that doesn't prevent same-thread reentrancy (multiple calls to BeginProcess on the original thread).

like image 527
Adam Maras Avatar asked Sep 25 '13 23:09

Adam Maras


People also ask

What does lock () do in C#?

The lock statement acquires the mutual-exclusion lock for a given object, executes a statement block, and then releases the lock. While a lock is held, the thread that holds the lock can again acquire and release the lock. Any other thread is blocked from acquiring the lock and waits until the lock is released.

What is reentrant lock in C#?

A reentrant lock is a mutual exclusion mechanism that allows threads to reenter into a lock on a resource (multiple times) without a deadlock situation.

What is Reentrancy Java?

The Java runtime system allows a thread to re-acquire a monitor that it already holds because Java monitors are reentrant. Reentrant monitors are important because they eliminate the possibility of a single thread deadlocking itself on a monitor that it already holds.

What is the difference between lock and monitor in C#?

Both Monitor and lock provides a mechanism that synchronizes access to objects. lock is the shortcut for Monitor. Enter with try and finally. Lock is a shortcut and it's the option for the basic usage.


2 Answers

There is one synchronization object in .NET that isn't re-entrant, you are looking for a Semaphore.

Before you commit to this, do get your ducks in a row and ask yourself how it can be possible that BeginProcess() can be called again on the same thread. That is very, very unusual, your code has to be re-entrant for that to happen. This can normally only happen on a thread that has a dispatcher loop, the UI thread of a GUI app is a common example. If this is truly possible and you actually use a Semaphore then you'll get to deal with the consequence as well, your code will deadlock. Since it recursed into BeginProcess and stalls on the semaphore. Thus never completing and never able to call EndProcess(). There's a good reason why Monitor and Mutex are re-entrant :)

like image 171
Hans Passant Avatar answered Oct 01 '22 18:10

Hans Passant


You can use Semaphore class which came with .NET Framework 2.0.

A good usage of Semaphores is to synchronize limited amount of resources. In your case it seems you have resources like Context which you want to share between consumers.

You can create a semaphore to manage the resources like:

var resourceManager = new Semaphore(0, 10);

And then wait for a resource to be available in the BeginProcess method using:

resourceManager.WaitOne();

And finally free the resource in the EndProcess method using:

resourceManager.Release();

Here's a good blog about using Semaphores in a situation like yours:

https://web.archive.org/web/20121207180440/http://www.dijksterhuis.org/using-semaphores-in-c/

like image 20
mehrandvd Avatar answered Oct 01 '22 18:10

mehrandvd