InMemoryCache 在刷新(长时间运行)缓存期间返回旧缓存 .net Framework 4.8

问题描述 投票:0回答:1

我实现了以下 InMemoryCache 机制,如何确定如果为新缓存获取数据需要很长时间,其他线程能够毫无问题地获取旧缓存数据?

public class InMemoryCache <TData>
{
    private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim();
    private readonly Timer _refreshTimer;
    private DateTime _lastRefreshTime;
    private readonly int _refreshDataIntervalInMinutes;
    private TData _cachedData;
    private readonly Func <TData> _dataFetchingMethod;
    private bool _isRefreshing;

    public InMemoryCache(int ? refreshDataIntervalInMinutes, Func <TData> dataFetchingMethod)
    {
        _dataFetchingMethod = dataFetchingMethod;
        if (ShouldStartTimer(refreshDataIntervalInMinutes))
        {
            if (refreshDataIntervalInMinutes != null) _refreshDataIntervalInMinutes = refreshDataIntervalInMinutes.Value;
            _refreshTimer = new Timer(RefreshData, null, TimeSpan.FromMinutes(_refreshDataIntervalInMinutes), TimeSpan.FromMinutes(_refreshDataIntervalInMinutes));
        }
        RefreshData(null);
    }
    private static bool ShouldStartTimer(int ? refreshDataIntervalInMinutes)
    {
        return refreshDataIntervalInMinutes != null;
    }

    public TData GetData()
    {
        _lockSlim.EnterReadLock();
        try
        {
            if (DateTime.Now - _lastRefreshTime > TimeSpan.FromMinutes(_refreshDataIntervalInMinutes) && TryEnterRefreshLock())
            {
                RefreshData(null);
            }
            return _cachedData;
        }
        finally
        {
            _lockSlim.ExitReadLock();
        }
    }

    private bool TryEnterRefreshLock()
    {

            int isRefreshing = _isRefreshing ? 1 : 0;

            //for atomic check

            return Interlocked.CompareExchange(ref isRefreshing, 1, 0) == 0;

    }

    private void RefreshData(object state)
    {
        _lockSlim.EnterUpgradeableReadLock();
        try
        {
            if (_isRefreshing) return;
            
            _lockSlim.EnterWriteLock();
            _isRefreshing = true;
            try
            {
                _cachedData = _dataFetchingMethod.Invoke();
                _lastRefreshTime = DateTime.Now;
            }
            finally
            {
                _isRefreshing = false;
                _lockSlim.ExitWriteLock();
            }
        }
        finally
        {
            _lockSlim.ExitUpgradeableReadLock();
        }
    }

    public void ManualRefresh()
    {
        Task.Run(() => RefreshData(null));
    }
}
c# multithreading thread-safety
1个回答
0
投票

您在确保使用 InMemoryCache 实现检索新数据时其他线程仍然可以访问以前缓存的数据方面做得很好。然而,为了保证线程安全和缓存机制的正确操作,需要进行一些增强和修改。

处理线程安全刷新标志: => _isRefreshing 的使用和测试方式并不能保证线程安全。为了保证原子性,您应该使用互锁操作。

优化锁定策略=>应该使用ReaderWriterLockSlim,但是通过优化获取锁的方法还有提高并发性和最小化锁争用的空间。

处理刷新间隔检查 => 如果数据获取过程需要很长时间,GetData 方法对刷新间隔的检查可能会导致不必要的刷新。

改进了 InMemoryCache 类:

public class InMemoryCache<TData>
{
    private readonly ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim();
    private readonly Timer _refreshTimer;
    private DateTime _lastRefreshTime;
    private readonly TimeSpan _refreshDataInterval;
    private TData _cachedData;
    private readonly Func<TData> _dataFetchingMethod;
    private int _isRefreshing;

    public InMemoryCache(int? refreshDataIntervalInMinutes, Func<TData> dataFetchingMethod)
    {
        _dataFetchingMethod = dataFetchingMethod ?? throw new ArgumentNullException(nameof(dataFetchingMethod));
        _refreshDataInterval = TimeSpan.FromMinutes(refreshDataIntervalInMinutes ?? 0);
        _refreshTimer = new Timer(RefreshData, null, _refreshDataInterval, _refreshDataInterval);
        RefreshData(null);
    }

    public TData GetData()
    {
        _lockSlim.EnterReadLock();
        try
        {
            if (DateTime.Now - _lastRefreshTime > _refreshDataInterval && Interlocked.CompareExchange(ref _isRefreshing, 1, 0) == 0)
            {
                Task.Run(() => RefreshData(null));
            }
            return _cachedData;
        }
        finally
        {
            _lockSlim.ExitReadLock();
        }
    }

    private void RefreshData(object state)
    {
        if (!Monitor.TryEnter(this)) return;
        try
        {
            _lockSlim.EnterWriteLock();
            try
            {
                _cachedData = _dataFetchingMethod.Invoke();
                _lastRefreshTime = DateTime.Now;
            }
            finally
            {
                _lockSlim.ExitWriteLock();
                Interlocked.Exchange(ref _isRefreshing, 0);
            }
        }
        finally
        {
            Monitor.Exit(this);
        }
    }

    public void ManualRefresh()
    {
        Task.Run(() => RefreshData(null));
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.