.NET Framework 4.8中InMemoryCache刷新时如何保障旧缓存可读
缓存刷新时旧数据读取安全性分析
结论先行
你的这个缓存实现,在获取新数据耗时较长的场景下,其他线程完全可以正常读取旧缓存数据,不会被阻塞或出现异常。
为什么能安全读取?
1. 读写锁的特性保障
你使用的ReaderWriterLockSlim是专门优化读写场景的同步锁:
- 多个读线程可以同时持有读锁,互相不阻塞,只要有线程在读取旧数据,其他线程也能同步读取。
- 只有当写操作(缓存刷新)启动时,才会阻止新的读请求进入,但已经持有读锁的线程可以继续读取旧数据,直到完成操作释放读锁。
2. 刷新逻辑的并发控制
- 你设计的
TryEnterRefreshLock()意图是防止多线程重复触发刷新,核心思路是确保同一时间只有一个线程执行耗时的数据获取操作。 - 刷新时,
RefreshData()会先获取可升级读锁,确认未在刷新后才升级为写锁。在写锁更新_cachedData之前,所有读线程访问的都是旧缓存值,完全不受刷新操作影响。
你的实现里的关键问题
虽然旧数据读取安全,但存在两个需要修复的bug:
TryEnterRefreshLock()逻辑无效:你将_isRefreshing转换为局部变量后,用Interlocked.CompareExchange操作的是局部变量,并未修改原字段_isRefreshing,等于没做并发控制,会导致多个线程同时触发刷新。建议把_isRefreshing改成int类型(0=未刷新,1=刷新中),直接操作原字段做原子控制。- 读锁下调用刷新会阻塞请求:在
GetData()持有读锁时调用RefreshData(),后者获取可升级读锁会被卡住,直到GetData()释放读锁。这会导致触发刷新的那个读请求被阻塞到刷新完成,影响性能。建议将刷新逻辑异步执行,避免阻塞读请求。
修正后的关键代码示例
首先修改_isRefreshing类型和刷新锁逻辑:
// 将bool类型改为int,0表示未刷新,1表示正在刷新 private int _isRefreshing; private bool TryEnterRefreshLock() { // 原子操作:如果当前值是0,则设置为1,返回原值0表示成功获取刷新锁 return Interlocked.CompareExchange(ref _isRefreshing, 1, 0) == 0; }
然后修正RefreshData()中的状态更新逻辑:
private void RefreshData(object state) { _lockSlim.EnterUpgradeableReadLock(); try { // 检查是否已有线程在刷新,有则直接返回 if (Interlocked.CompareExchange(ref _isRefreshing, 1, 0) != 0) return; _lockSlim.EnterWriteLock(); try { _cachedData = _dataFetchingMethod.Invoke(); _lastRefreshTime = DateTime.Now; } finally { // 刷新完成后重置状态 Interlocked.Exchange(ref _isRefreshing, 0); _lockSlim.ExitWriteLock(); } } finally { _lockSlim.ExitUpgradeableReadLock(); } }
内容的提问来源于stack exchange,提问作者Kamil Ibadov
相关产品推荐
相关产品推荐

