ASP.NET Core 9中如何原子操作IMemoryCache内的List<string>?
解决IMemoryCache中List的竞态条件问题
首先纠正你代码里的一个明显bug:你创建了原列表的副本listCopy,但却往原列表idsList里添加了值,最后存回缓存的是listCopy——这会导致新值根本没被存入缓存,正确写法应该是往listCopy里添加值再存回去。
回到竞态条件的问题,有两种更合理的解决方案,比自行实现全局锁更高效:
方案一:使用线程安全集合替代List
把缓存中存储的对象从List<string>换成线程安全集合,比如ConcurrentBag<string>(无序场景)或ConcurrentQueue<string>(有序队列场景),这类集合的Add操作本身就是线程安全的,无需取出-复制-存回的流程,直接操作即可:
var cacheKey = CacheKey(document.Resource.PartitionKey()); // 取出缓存中的线程安全集合,不存在则自动创建 var idsBag = _memoryCache.GetOrCreate(cacheKey, entry => new ConcurrentBag<string>()); // 直接添加值,无需额外处理线程安全 idsBag.Add(document.Resource.Id);
这种方式最简便,完全规避了竞态问题,还省去了复制列表的性能开销。如果需要有序集合,ConcurrentQueue<string>是更合适的选择。
方案二:针对缓存Key的细粒度锁
如果必须使用List<string>,不要用全局锁,而是为每个缓存Key创建独立的锁对象,避免不同Key的操作互相阻塞:
首先定义静态的锁容器:
private static readonly ConcurrentDictionary<string, object> _cacheLocks = new ConcurrentDictionary<string, object>();
然后修改缓存操作逻辑:
var cacheKey = CacheKey(document.Resource.PartitionKey()); // 获取当前Key对应的锁对象,不存在则自动创建 var lockObj = _cacheLocks.GetOrAdd(cacheKey, k => new object()); lock (lockObj) { var idsList = _memoryCache.Get(cacheKey) as List<string>; if (idsList != null) { var listCopy = new List<string>(idsList); listCopy.Add(document.Resource.Id); _memoryCache.Set(cacheKey, listCopy); } }
这种方式保证了同一Key的操作串行执行,不同Key的操作互不影响,性能远优于全局锁。
不推荐方案:全局锁
不要使用单一的全局锁对象,这会导致所有缓存Key的操作都串行执行,严重拖累并发性能,仅极端场景下才考虑。
内容的提问来源于stack exchange,提问作者BenjiFB
相关产品推荐
相关产品推荐

