33

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

是否有任何内置方法来锁定 asp.net mvc 操作?

谢谢

4

6 回答 6

62

你在寻找这样的东西吗?

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关键字后面括号中的内容,aka Lock, Jerry, or Samantha,应该标记为一个static字段)。在执行锁定部分的持续时间内,它“保持”锁定对象。当线程退出锁定部分时,它“放弃”锁定对象。从线程获取锁对象的那一刻起,直到它放弃锁对象,所有其他线程都被阻止进入锁定的代码段。实际上,它们被“暂停”,直到当前正在执行的线程放弃锁定对象。

因此,线程 A 在 MyAction 方法的开头获取锁定对象。在它放弃锁对象之前,线程 B 也尝试执行这个方法。但是,它无法获取锁对象,因为它已经被线程 A 持有。所以它等待线程 A 放弃锁对象。当它完成时,线程 B 然后拿起锁对象并开始执行代码块。当线程 B 完成执行该块时,它为下一个委托处理此方法的线程放弃锁定对象。

...但我不确定这是否是您正在寻找的...

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

请记住,我上面提供的代码会导致其他线程在执行块之前等待。如果这不是您想要的,并且如果它已经被另一个线程执行,并且您希望整个操作被“跳过”,那么使用更像 Oshry 的答案的东西。您可以将此信息存储在缓存、会话或任何其他数据存储机制中。

于 2012-12-16T14:52:28.520 回答
7

我更喜欢使用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
    }
}

我希望它有所帮助。

于 2019-10-17T23:07:15.793 回答
6

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

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

使用@danludwig 详述的相同静态锁定对象

于 2014-02-28T16:22:55.987 回答
2

您可以[UseLock]根据您的要求创建自定义属性并将其放在您的操作之前

于 2012-12-16T11:55:47.403 回答
1

我对此有一些建议。

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

2- 具有 [DisableConcurrentExecution(1000)] 属性的 Hangfire BackgroundJob.Enqueue。

两个解决方案正在等待处理完成。我不想在同时请求时抛出错误。

于 2017-10-03T15:17:37.310 回答
-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
}

当然,你也可以这样做,或者类似的东西,作为一个属性。

于 2012-12-16T12:18:09.283 回答