Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to lock an asp.net mvc action?

Tags:

asp.net-mvc

I've written a controller and action that I use as a service. This service runs quite a costly action. I'd like to limit the access to this action if there is already a currently running action.

Is there any built in way to lock an asp.net mvc action?

Thanks

like image 262
vondip Avatar asked Dec 16 '12 11:12

vondip


People also ask

What is action in ASP NET MVC?

ASP.NET MVC - Actions. ASP.NET MVC Action Methods are responsible to execute requests and generate responses to it. By default, it generates a response in the form of ActionResult. Actions typically have a one-to-one mapping with user interactions.

What is the default action method in MVC?

By default, Controller is the Home Controller and default Action Method is the “Index” action method. Interview questions for freshers on the Action Method. What is the Action Method in ASP.NET MVC/MVC 5?

Can action methods be private in MVC?

So, every public method inside the Controller is an action method in MVC. Action Method can not be a private or protected method. If you provide the private or protected access modifier to the action method, it will provide the error to the user, i.e., “resource can not be found” as below.

How to restrict action method to render on the browser in MVC?

All the public methods of the MVC Controller are action methods. If we want the public method to be a non-action method, then we can decorate the action method by “NonAction” attribute. It will restrict the action method to render on the browser. To work with action method we need to remember the following points.


4 Answers

Are you looking for something like this?

public MyController : Controller
{
    private static object Lock = new object();

    public ActionResult MyAction()
    {
        lock (Lock)
        {
            // do your costly action here
        }    
    }
}

The above will prevent any other threads from executing the action if a thread is currently processing code within the lock block.

Update: here is how this works

Method code is always executed by a thread. On a heavily-loaded server, it is possible for 2 or more different threads to enter and begin executing a method in parallel. According to the question, this is what you want to prevent.

Note how the private Lock object is static. This means it is shared across all instances of your controller. So, even if there are 2 instances of this controller constructed on the heap, both of them share the same Lock object. (The object doesn't even have to be named Lock, you could name it Jerry or Samantha and it would still serve the same purpose.)

Here is what happens. Your processor can only allow 1 thread to enter a section of code at a time. Under normal circumstances, thread A could begin executing a code block, and then thread B could begin executing it. So in theory you can have 2 threads executing the same method (or any block of code) at the same time.

The lock keyword can be used to prevent this. When a thread enters a block of code wrapped in a lock section, it "picks up" the lock object (what is in parenthesis after the lock keyword, a.k.a. Lock, Jerry, or Samantha, which should be marked as a static field). For the duration of time where the locked section is being executed, it "holds onto" the lock object. When the thread exits the locked section, it "gives up" the lock object. From the time the thread picks up the lock object, until it gives up the lock object, all other threads are prevented from entering the locked section of code. In effect, they are "paused" until the currently executing thread gives up the lock object.

So thread A picks up the lock object at the beginning of your MyAction method. Before it gives up the lock object, thread B also tries to execute this method. However, it cannot pick up the lock object because it is already held by thread A. So it waits for thread A to give up the lock object. When it does, thread B then picks up the lock object and begins executing the block of code. When thread B is finished executing the block, it gives up the lock object for the next thread that is delegated to handle this method.

... but I'm not sure if this is what you are looking for...

Using this approach will not necessarily make your code run any faster. It only ensures that a block of code can only be executed by 1 thread at a time. It is usually used for concurrency reasons, not performance reasons. If you can provide more information about your specific problem in the question, there may be a better answer than this one.

Remember that the code I presented above will cause other threads to wait before executing the block. If this is not what you want, and you want the entire action to be "skipped" if it is already being executed by another thread, then use something more like Oshry's answer. You can store this info in cache, session, or any other data storage mechanism.

like image 82
danludwig Avatar answered Oct 21 '22 07:10

danludwig


I prefer to use SemaphoreSlim because it support async operations.

If you need to control the read/write then you can use the ReaderWriterLockSlim.

The following code snip uses the SemaphoreSlim:

public class DemoController : Controller
{
    private static readonly SemaphoreSlim ProtectedActionSemaphore =
        new SemaphoreSlim(1);

    [HttpGet("paction")] //--or post, put, delete...
    public IActionResult ProtectedAction()
    {
        ProtectedActionSemaphore.Wait();
        try
        {
            //--call your protected action here
        }
        finally
        {
            ProtectedActionSemaphore.Release();
        }

        return Ok(); //--or any other response
    }

    [HttpGet("paction2")] //--or post, put, delete...
    public async Task<IActionResult> ProtectedActionAsync()
    {
        await ProtectedActionSemaphore.WaitAsync();
        try
        {
            //--call your protected action here
        }
        finally
        {
            ProtectedActionSemaphore.Release();
        }

        return Ok(); //--or any other response
    }
}

I hope it helps.

like image 38
dbvega Avatar answered Oct 21 '22 08:10

dbvega


Having read and agreed with the above answer I wanted a slightly different solution: If you want to detect a second call to an action, use Monitor.TryEnter:

if (!Monitor.TryEnter(Lock, new TimeSpan(0)))
{
    throw new ServiceBusyException("Locked!");
}
try
{
...
}
finally {
    Monitor.Exit(Lock);
}

Use the same static Lock object as detailed by @danludwig

like image 20
Seb Avatar answered Oct 21 '22 07:10

Seb


You can create a custom attribute like [UseLock] as per your requirements and put it before your Action

like image 42
Bhushan Firake Avatar answered Oct 21 '22 06:10

Bhushan Firake