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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 12:58:54