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

C# ASP.NET Core 6 Web API中MemoryCache的并行读写问题

解决ASP.NET Core 6中MemoryCache并发修改列表的竞态问题

这是典型的竞态条件导致的数据丢失:多个请求同时读取缓存中的旧列表,各自修改后写回缓存,后续请求的写入会覆盖前面的修改,最终只有最后一次写入的内容保留。以下是两种实用的解决方案:

方案1:线程安全集合 + 原子初始化

将缓存中的普通List<string>替换为线程安全集合,结合IMemoryCache.GetOrCreate方法确保集合初始化的原子性,后续直接操作集合即可(无需重复写回缓存,因为集合是引用类型,修改的是缓存中存储的同一个实例)。

// 从缓存中获取或初始化线程安全集合
var myConcurrentList = _cache.GetOrCreate("myCache", entry =>
{
    entry.AbsoluteExpiration = DateTime.Now.AddDays(2);
    entry.SlidingExpiration = TimeSpan.FromDays(2);
    entry.Size = 1;
    // 根据需求选择合适的线程安全集合:ConcurrentBag/ConcurrentQueue/ConcurrentStack
    return new ConcurrentBag<string>();
});

// 线程安全添加元素,无需额外同步
myConcurrentList.Add(someString);
  • GetOrCreate确保只有一个线程执行集合初始化逻辑,避免并发创建多个实例;
  • 线程安全集合的Add方法本身支持并发操作,不会出现元素丢失。

方案2:显式锁控制读写流程

如果必须使用普通List<string>,可以通过全局锁对象同步整个"读-改-写"流程,确保同一时间只有一个线程操作缓存。

// 定义静态锁对象(必须全局共享,否则每个控制器实例的锁起不到同步作用)
private static readonly object _cacheSyncLock = new object();

// 操作缓存时加锁
lock (_cacheSyncLock)
{
    if (!_cache.TryGetValue("myCache", out List<string>? myList))
    {
        myList = new List<string>();
    }

    var cacheEntryOptions = new MemoryCacheEntryOptions
    {
        AbsoluteExpiration = DateTime.Now.AddDays(2),
        SlidingExpiration = TimeSpan.FromDays(2),
        Size = 1,
    };

    myList.Add(someString);
    _cache.Set("myCache", myList, cacheEntryOptions);
}
  • 锁会阻止多个线程同时进入修改逻辑,确保每次修改都基于最新的缓存数据;
  • 该方案实现简单,但高并发场景下会有锁竞争的性能开销,适合并发量不高的场景。

方案选择建议

  • 高并发场景优先选方案1,初始化后无锁开销,性能更优;
  • 仅需兼容现有普通列表逻辑时选方案2,改动量小。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 08:45:57