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
相关产品推荐
相关产品推荐

