0

我正在开发一个用作缓存的线程安全类,它应该可以在 .NET 和 Mono 中工作。

项目有生存时间,每次检索对象时,都会刷新其生存时间。每次我添加一个项目时,时间戳都会添加到另一个拥有相同键的集合中。计时器引发了查找过时项目并将其删除的方法。

当我尝试获取和项目时,我还必须提供一个委托,指示如果缓存中不存在它如何获取它。

我已经进行了测试,虽然在测试中应该每 30 秒删除一次项目,但它经常发生,几乎每秒发生一次,我不知道为什么。

这是课程:

    public class GenericCache<TId, TItem>:IDisposable where TItem : class
{
    SortedDictionary<TId, TItem> _cache;
    SortedDictionary<TId, DateTime> _timeouts;
    Timer _timer;
    Int32 _cacheTimeout;
    System.Threading.ReaderWriterLockSlim _locker;

    public GenericCache(Int32 minutesTTL)
    {
        _locker = new System.Threading.ReaderWriterLockSlim();
        _cacheTimeout = minutesTTL;
        _cache = new SortedDictionary<TId, TItem>();
        _timeouts = new SortedDictionary<TId, DateTime>();
        _timer = new Timer((minutesTTL * 60) / 2);
        _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
        _timer.AutoReset = true;
        _timer.Enabled = true;
        _timer.Start();
    }

    /// <summary>
    /// Get an item, if it doesn't exist, create it using the delegate
    /// </summary>
    /// <param name="id">Id for the item</param>
    /// <param name="create">A delegate that generates the item</param>
    /// <returns>The item</returns>
    public TItem Get(TId id, Func<TItem> create)
    {
        _locker.EnterUpgradeableReadLock();
        try
        {
            TItem item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault();
            if (item == null)
            {
                _locker.EnterWriteLock();
                // check again, maybe another thread is waiting in EnterWriteLock cos the same item is null
                item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault();
                if (item == null)
                {
                    Debug.Write("_");
                    item = create.Invoke();
                    if (item != null)
                    {
                        _cache.Add(id, item);
                        _timeouts.Add(id, DateTime.Now);
                    }
                }
            }
            else
                _timeouts[id] = DateTime.Now;

            return item;
        }
        finally
        {
            if(_locker.IsWriteLockHeld)
                _locker.ExitWriteLock();
            _locker.ExitUpgradeableReadLock();
        }
    }

    /// <summary>
    /// Execute a delegate in the items, for example clear nested collections.
    /// </summary>
    /// <param name="action">The delegate</param>
    public void ExecuteOnItems(Action<TItem> action)
    {
        _locker.EnterWriteLock();
        try
        {
            foreach (var i in _cache.Values)
                action.Invoke(i);
        }
        finally
        {
            _locker.ExitWriteLock();
        }
    }

    /// <summary>
    /// Clear this cache
    /// </summary>
    public void Clear()
    {
        _locker.EnterWriteLock();
        try
        {
            _cache.Clear();
            _timeouts.Clear();
        }
        finally
        {
            _locker.ExitWriteLock();
        }
    }

    /// <summary>
    /// Remove outdated items
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void _timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        _locker.EnterUpgradeableReadLock();
        try
        {
            var delete = _timeouts.Where(to => DateTime.Now.Subtract(to.Value).TotalMinutes > _cacheTimeout).ToArray();

            if(delete.Any())
            {
                _locker.EnterWriteLock();
                foreach (var timeitem in delete)
                {
                    Debug.Write("-");
                    _cache.Remove(timeitem.Key);
                    _timeouts.Remove(timeitem.Key);
                }
            }
        }
        finally
        {
            if(_locker.IsWriteLockHeld)
                _locker.ExitWriteLock();
            _locker.ExitUpgradeableReadLock();
        }
    }

    #region IDisposable Members
    private volatile Boolean disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            try
            {
                this.Clear();
            }
            finally
            {
                _locker.Dispose();
            }

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~GenericCache()
    {
        Dispose(false);
    }

    #endregion
}

如您所见,在调试模式下,添加项目时会打印“_”符号,删除项目时会打印“-”符号。在测试中,第二分钟后可以看到项目是如何在同一秒内被删除和添加的,此时项目应该每 30 秒才被删除一次,我不知道为什么:

这就是我测试的方式:

        static void Main(string[] args)
    {
        GenericCache<Int32, String> cache = new GenericCache<Int32, String>(1);

        Debug.Listeners.Add(new ConsoleTraceListener());

        Action a = delegate()
        {
            Random r = new Random(DateTime.Now.Millisecond);
            while (true)
            {
                Int32 number = r.Next(0, 9999);
                if (String.IsNullOrEmpty(cache.Get(number, () => number.ToString())))
                    Debug.Write("E");
                Thread.Sleep(number);
            }
        };

        for (int i = 0; i < 150; i++)
        {
            new Thread(new ThreadStart(a)).Start();
            Thread.Sleep(5);
        }

        Console.ReadKey();
    }

您在 GenericCache 类中看到任何问题吗?

在此先感谢,亲切的问候。

4

1 回答 1

1

我看到的第一个问题(假设您使用 System.Timers.Timer 接受毫秒并且您正在传递秒数)。

  _timer = new Timer((minutesTTL * 60000) / 2); 
于 2010-04-06T15:08:18.920 回答