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

C# MVC并发调用POST方法时如何实现同步临界区?

.NET MVC并发请求下临界区实现问题解答

问题背景

我用C# .NET开发MVC项目时遇到了并发问题:控制器的PostDog POST接口被客户端每秒多次调用,因为MVC会并行处理请求,导致AddDog方法里的临界区代码并发执行,出现数据丢失。

原控制器代码:

[HttpPost]
public IActionResult PostDog(){
    AddDog(TempData, new Dog());
    return Json("success");
}

AddDog临界区代码:

public bool AddDog(ITempDataDictionary tempData, Dog dog){
    /* 临界区 - 并发执行会丢失数据 */
    List<Dog> dogs = (List<Dog>)tempData["myDogList"];
    dogs.Add(dog);
    tempData["myDogList"] = dogs;
    /* 临界区结束 */
    return true;
}

我尝试用lock解决,但发现TempData根本没更新,怀疑和TempData的传递方式有关,尝试的代码如下:

private static readonly object TempDataLock = new();

public static bool AddDog(ITempDataDictionary tempData, Dog dog){
    lock(TempDataLock )
    {
        List<Dog> dogs = (List<Dog>)tempData["myDogList"];
        dogs.Add(dog);
        tempData["myDogList"] = dogs;
    }
    return true;
}

问题根源

你用lock没用的核心原因是用错了存储载体:

  • TempData是请求级别的独立对象,每个HTTP请求都会创建自己的TempData实例,存储的是当前请求的临时数据(设计用于重定向这类请求间的数据传递),根本不是跨请求共享的集合。
  • 你加的静态锁虽然能锁住代码执行,但每个请求操作的都是自己TempData里的myDogList副本,完全不是同一个集合,自然锁了也白锁,更不可能实现跨请求的数据同步。

正确解决方案

方案1:全局共享线程安全集合(适合所有请求共享数据场景)

如果你的需求是让所有客户端的请求都往同一个Dog列表里加数据,直接用.NET自带的线程安全集合(比如ConcurrentBag<T>),这类集合的操作本身就是线程安全的,无需额外加锁:

// 全局静态线程安全集合,所有请求共享
private static readonly ConcurrentBag<Dog> _globalDogList = new ConcurrentBag<Dog>();

[HttpPost]
public IActionResult PostDog(){
    AddDog(new Dog());
    return Json("success");
}

public bool AddDog(Dog dog){
    // ConcurrentBag.Add本身线程安全,直接调用即可
    _globalDogList.Add(dog);
    return true;
}

如果需要有序的列表(ConcurrentBag是无序的),可以用静态List<T>配合lock:

private static readonly List<Dog> _globalDogList = new List<Dog>();
// 静态锁对象,确保所有请求都用同一把锁
private static readonly object _listLock = new object();

[HttpPost]
public IActionResult PostDog(){
    AddDog(new Dog());
    return Json("success");
}

public bool AddDog(Dog dog){
    lock(_listLock){
        _globalDogList.Add(dog);
    }
    return true;
}

方案2:Session存储(适合单个用户跨请求共享数据场景)

如果你的需求是每个用户的请求只往自己的Dog列表里加数据,那应该用Session(每个用户有独立的Session),同时处理Session的并发访问:

[HttpPost]
public async Task<IActionResult> PostDog(){
    await AddDogAsync(new Dog());
    return Json("success");
}

public async Task<bool> AddDogAsync(Dog dog){
    // 先加载Session中的数据
    await HttpContext.Session.LoadAsync();
    
    // 锁定当前用户的Session,防止同一用户的并发请求冲突
    lock(HttpContext.Session){
        // 从Session中获取Dog列表,不存在则新建
        var dogs = HttpContext.Session.Get<List<Dog>>("myDogList") ?? new List<Dog>();
        dogs.Add(dog);
        // 将更新后的列表存回Session
        HttpContext.Session.Set("myDogList", dogs);
    }
    
    // 保存Session变更
    await HttpContext.Session.CommitAsync();
    return true;
}

注意:需要先配置Session的序列化方式,比如在Program.cs中添加:

builder.Services.AddSession(options => {
    options.IdleTimeout = TimeSpan.FromMinutes(30);
});
// 配置Session的序列化(以System.Text.Json为例)
builder.Services.AddControllersWithViews()
    .AddJsonOptions(options => {
        options.JsonSerializerOptions.PropertyNamingPolicy = null;
    });

// 注册Session序列化扩展方法
public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T? Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }
}

关键总结

  1. 永远不要用TempData做跨请求的共享存储,它的设计目标是请求间临时传递数据,不是共享集合。
  2. 全局共享数据优先用线程安全集合,或者静态集合+全局锁。
  3. 单个用户的跨请求数据用Session,并注意处理Session的并发锁定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:57:38