You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止控制器Action被并发多次执行?

问题分析与优化解答

背景与初始疑问

涉事方法负责查询并聚合第三方API的结果,请求量过大时可能导致服务失控。我原本计划在Autofac中将控制器注册为单例,通过锁和实例字段跟踪方法执行状态,想知道这种实现是否简洁,有没有Bug或竞态条件。

初始实现代码

public class ExpensiveController : Controller
{
    private bool _alreadyRunning = false;
    private static readonly object Instancelock = new object();

    [HttpGet("expensiveController/CallThirdPartyApi")]
    public ActionResult<IEnumerable<MyObject>> CallThirdPartyApi()
    {
        // Critical Section Start
        lock (Instancelock)
        { 
            if (_alreadyRunning == true)
            {
                return Ok; // 语法错误,缺少括号
            }
            else
            {
                _alreadyRunning = true;
            }
        } //Critical Section End
              
        // do work
        var list = DoExpensiveProcessing();
        _alreadyRunning = false;

        return list;
    }
}

初始实现的问题

  1. 控制器单例的设计风险:ASP.NET Core控制器默认是瞬态生命周期,强行改成单例会违背框架设计原则,后续扩展控制器功能时极易引入线程安全问题。
  2. 核心逻辑Bug:_alreadyRunning是实例字段,但锁对象Instancelock是静态的。如果控制器是瞬态(默认),每个请求都会创建新的控制器实例,每个实例都有自己的_alreadyRunning,锁虽然是全局的,但检查的是当前实例的状态,完全起不到“阻止并发执行”的作用;就算改成单例,_alreadyRunning = false;未在锁内执行,代码逻辑不严谨,后续修改容易出问题。
  3. 语义与语法问题:返回Ok缺少括号属于语法错误,且用200状态码返回“服务正忙”的结果不符合HTTP语义,调用方无法正确识别状态。

优化后的实现代码

public class ExpensiveController : Controller
{
    private static bool _alreadyRunning;
    private static readonly object InstanceLock = new object();

    [HttpGet("expensiveController/CallThirdPartyApi")]
    public ActionResult<IEnumerable<MyObject>> CallThirdPartyApi()
    {
        lock (InstanceLock)
        {
            if (_alreadyRunning)
            {
                return new ServiceUnavailableResult();
            }
            _alreadyRunning = true;
        }

        var list = DoExpensiveProcessing();
        
        lock (InstanceLock)
        {
            _alreadyRunning = false;
        }
        return list;
    }  
}

优化方案的优势

  1. 脱离控制器生命周期约束:改用静态字段_alreadyRunning跟踪全局执行状态,无需修改控制器默认的瞬态生命周期,避免了单例带来的潜在风险。
  2. 彻底消除竞态条件:所有对_alreadyRunning的读写操作都在锁内完成,确保状态变更的原子性,完全阻止了多个请求同时进入执行逻辑的情况。
  3. 语义更准确:返回ServiceUnavailableResult(503 HTTP状态码),明确告诉调用方“服务暂时不可用”,符合HTTP规范。
  4. 代码更简洁严谨:逻辑清晰,锁的范围精准,没有冗余嵌套,也修正了语法问题。

内容的提问来源于stack exchange,提问作者Mitch A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 21:45:14