我编写了一个控制器和动作,将其用作服务。该服务运行成本相当高的操作。如果已经有当前正在运行的操作,我希望限制对此操作的访问。
是否有内置的方法来锁定 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
关键字后面的括号中,也就是说,在Lock
、Jerry
或Samantha
之后,应该标记为static
字段)。 在执行锁定部分的持续时间内,它将“保持住”锁对象。 当线程退出锁定部分时,它会“放弃”锁对象。从线程拿起锁对象的时间开始,直到它放弃锁对象为止,所有其他线程都被阻止进入受锁定的代码段。实际上,它们被“暂停”,直到当前正在执行的线程放弃锁对象。
因此,线程A在开始MyAction方法时获取了锁对象。在放弃锁对象之前,线程B还尝试执行此方法。但是,由于锁已经被线程A持有,因此它无法获取锁对象。因此,它等待线程A放弃锁对象。当它这样做时,线程B然后拾起锁对象并开始执行代码块。线程B完成执行代码块后,释放锁对象以供委派处理此方法的下一个线程使用。
… 但我不确定您是否正在寻找这种方式。。。
使用此方法并不一定会使您的代码运行更快。它只确保一段代码只能由1个线程执行。通常是出于并发原因而不是出于性能原因而使用的。如果您可以在问题中提供有关特定问题的更多信息,则可能会有比此答案更好的答案。
请记住,我提供的代码将导致其他线程在执行该块之前等待。如果这不是您想要的,如果您希望整个操作在另一个线程已经执行时被“跳过”,则可以使用类似Oshry's answer的东西。您可以将此信息存储在缓存、会话或任何其他数据存储机制中。
我更喜欢使用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
相同的效果。 - fullStackChrismaxCount=1
设置一下,我没有理会它是因为两个函数在调用 .Release()
前都在等待信号量,这意味着你永远不会允许超过一个项目并发。请在此处阅读更多关于该构造函数的信息。 - dbvega阅读并同意上述答案后,我想提供稍微不同的解决方案:如果要检测对某个操作的第二次调用,请使用Monitor.TryEnter:
if (!Monitor.TryEnter(Lock, new TimeSpan(0)))
{
throw new ServiceBusyException("Locked!");
}
try
{
...
}
finally {
Monitor.Exit(Lock);
}
使用与@danludwig详细介绍相同的静态锁对象
您可以根据需要创建自定义属性,例如 [UseLock]
,并将其放在Action之前
我对此有一些建议。
1- https://github.com/madelson/DistributedLock 这是一个系统范围的锁解决方案。
2- 使用Hangfire BackgroundJob.Enqueue和[DisableConcurrentExecution(1000)]属性。 这两种解决方案都需要等待进程完成。当同时请求时,我不希望抛出错误。
最简单的方法是将一个布尔值保存到缓存中,指示该操作已经运行所需的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
}
当然,你也可以将这个或类似的东西作为属性来实现。