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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:53:19