如何锁定ASP.NET MVC操作?

37

我编写了一个控制器和动作,将其用作服务。该服务运行成本相当高的操作。如果已经有当前正在运行的操作,我希望限制对此操作的访问。

是否有内置的方法来锁定 ASP.NET MVC 动作?

谢谢


1
您想按用户、会话或应用程序限制它吗? - Oshry
1
你正在寻找队列。 - Umur Kontacı
总体限制,如果已经被调用,则不允许任何人进入。 - vondip
1
“如果已被调用”指的是“如果正在执行”。 - sports
6个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
70

你是否在寻找类似于这样的东西?

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

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

如果一个线程正在lock块中处理代码,则上述方法将阻止其他线程执行该操作。

更新:这是它的工作原理

方法代码总是由一个线程执行的。在负载较重的服务器上,有可能会有2个或更多不同的线程并行进入并开始执行方法。根据问题,这是您想要防止的情况。

请注意,private Lock对象是static的。 这意味着它在您控制器的所有实例之间共享。因此,即使在堆上构造了2个此控制器的实例,它们两个都共享相同的Lock对象。 (对象甚至不必被命名为Lock,您可以将其命名为Jerry或Samantha,它仍然具有相同的目的。)

发生了什么事。您的处理器一次只能允许1个线程进入代码部分。 在正常情况下,线程A可以开始执行代码块,然后线程B可以开始执行它。因此,理论上您可以同时有2个线程执行相同的方法(或任何代码块)。

lock关键字可用于防止此情况发生。当线程进入用lock包装的代码块时,它会“拿起”锁对象(在lock关键字后面的括号中,也就是说,在LockJerrySamantha之后,应该标记为static字段)。 在执行锁定部分的持续时间内,它将“保持住”锁对象。 当线程退出锁定部分时,它会“放弃”锁对象。从线程拿起锁对象的时间开始,直到它放弃锁对象为止,所有其他线程都被阻止进入受锁定的代码段。实际上,它们被“暂停”,直到当前正在执行的线程放弃锁对象。

因此,线程A在开始MyAction方法时获取了锁对象。在放弃锁对象之前,线程B还尝试执行此方法。但是,由于锁已经被线程A持有,因此它无法获取锁对象。因此,它等待线程A放弃锁对象。当它这样做时,线程B然后拾起锁对象并开始执行代码块。线程B完成执行代码块后,释放锁对象以供委派处理此方法的下一个线程使用。

… 但我不确定您是否正在寻找这种方式。。。

使用此方法并不一定会使您的代码运行更快。它只确保一段代码只能由1个线程执行。通常是出于并发原因而不是出于性能原因而使用的。如果您可以在问题中提供有关特定问题的更多信息,则可能会有比此答案更好的答案。

请记住,我提供的代码将导致其他线程在执行该块之前等待。如果这不是您想要的,如果您希望整个操作在另一个线程已经执行时被“跳过”,则可以使用类似Oshry's answer的东西。您可以将此信息存储在缓存、会话或任何其他数据存储机制中。


锁定对象是否也应该被持久化到会话或缓存对象中? - vondip
你认为为什么需要将它持久化到会话或缓存中? - danludwig
由于HTTP是无状态的,我需要在会话期间保存锁定状态,不是吗? - vondip
1
HTTP可能是无状态的,但服务器方法是由线程执行的。会话仅仅是一种数据存储机制。您的HTTP请求由服务器处理,并从池中提取线程来执行您的服务器端方法。您不需要会话、缓存或任何其他数据存储机制来防止其他线程在另一个线程进入它之后但在另一个线程退出之前执行该方法。这种基于线程的方法锁定已经内置在服务器运行时中。我会更新我的答案,使其更加清晰明了。 - danludwig
使用锁是否确保所有对该方法的调用最终都会运行,并且每次释放锁时请求将按顺序运行?对于我的用例来说,使用队列并在操作之外进行处理并不一定适合,或者如果锁可以实现我们需要的功能,则不希望这样做。 - Dan Harris

11

我更喜欢使用SemaphoreSlim,因为它支持异步操作。

如果需要控制读/写,则可以使用ReaderWriterLockSlim

以下代码片段使用了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
    }
}

希望能对你有所帮助。


静态变量不应该是 new SemaphoreSlim(1,1) 吗?也就是说,你还应该将 maxCount 指定为 1,以达到与 lock 相同的效果。 - fullStackChris
1
@fullStackChris 你可以将 maxCount=1 设置一下,我没有理会它是因为两个函数在调用 .Release() 前都在等待信号量,这意味着你永远不会允许超过一个项目并发。请在此处阅读更多关于该构造函数的信息。 - dbvega

6

阅读并同意上述答案后,我想提供稍微不同的解决方案:如果要检测对某个操作的第二次调用,请使用Monitor.TryEnter:

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

使用与@danludwig详细介绍相同的静态锁对象


我尝试了相同的方法,但出现了问题。while (!Monitor.TryEnter(LockObj)) { Thread.Sleep(50); } try{ ... } finally { Monitor.Exit(LockObj); } - Seyed Ali Mahmoody
由于在未同步的代码块中调用了对象同步方法。 - Seyed Ali Mahmoody

2

您可以根据需要创建自定义属性,例如 [UseLock],并将其放在Action之前


2

我对此有一些建议。

1- https://github.com/madelson/DistributedLock 这是一个系统范围的锁解决方案。

2- 使用Hangfire BackgroundJob.Enqueue和[DisableConcurrentExecution(1000)]属性。 这两种解决方案都需要等待进程完成。当同时请求时,我不希望抛出错误。


-3

最简单的方法是将一个布尔值保存到缓存中,指示该操作已经运行所需的BL:

if (System.Web.HttpContext.Current.Cache["IsProcessRunning"])
{
    System.Web.HttpContext.Current.Cache["IsProcessRunning"] = true;
    // run your logic here
    System.Web.HttpContext.Current.Cache["IsProcessRunning"] = false
}

当然,你也可以将这个或类似的东西作为属性来实现。


5
在if语句内,你可以让两个线程汇合。但我担心这样还不够好。 - vondip
你如何让两个线程在if条件语句中同时运行? 这几乎是不可能的,但如果你必须绝对确定,你可以锁定缓存的赋值。 - Oshry
9
这是一个非常容易出现“脏”竞态条件错误的代码配方。如果两个线程执行同一个操作有可能会导致问题,就需要使用锁(lock)来解决。 - Vincent
1
这并不能防止竞态条件,即在变量更改为true之前,2个或多个线程同时执行IF的情况。 - Dave Lucre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,