我编写了一个控制器和操作,我将其用作服务。这项服务的成本很高。如果当前已经有一个正在运行的操作,我想限制对此操作的访问。
是否有任何内置方法来锁定 asp.net mvc 操作?
谢谢
我编写了一个控制器和操作,我将其用作服务。这项服务的成本很高。如果当前已经有一个正在运行的操作,我想限制对此操作的访问。
是否有任何内置方法来锁定 asp.net mvc 操作?
谢谢
你在寻找这样的东西吗?
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 的答案的东西。您可以将此信息存储在缓存、会话或任何其他数据存储机制中。
我更喜欢使用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
}
}
我希望它有所帮助。
阅读并同意上述答案后,我想要一个稍微不同的解决方案:如果要检测对操作的第二次调用,请使用 Monitor.TryEnter:
if (!Monitor.TryEnter(Lock, new TimeSpan(0)))
{
throw new ServiceBusyException("Locked!");
}
try
{
...
}
finally {
Monitor.Exit(Lock);
}
使用@danludwig 详述的相同静态锁定对象
您可以[UseLock]
根据您的要求创建自定义属性并将其放在您的操作之前
我对此有一些建议。
1- https://github.com/madelson/DistributedLock 系统范围的锁解决方案
2- 具有 [DisableConcurrentExecution(1000)] 属性的 Hangfire BackgroundJob.Enqueue。
两个解决方案正在等待处理完成。我不想在同时请求时抛出错误。
最简单的方法是将一个布尔值保存到缓存中,指示该操作已经在运行所需的 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
}
当然,你也可以这样做,或者类似的东西,作为一个属性。