【问题标题】:Class for mantain a thread safe cache维护线程安全缓存的类
【发布时间】:2010-04-06 14:46:57
【问题描述】:

我正在开发一个线程安全类,我将用作缓存,它应该在 .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 类有什么问题吗?

提前致谢,问候。

【问题讨论】:

  • 为什么要重新发明轮子? EL 缓存有什么问题?
  • 为什么不使用企业库缓存块?它是线程安全的。 msdn.microsoft.com/en-us/library/cc309103.aspx
  • @Randolpho 正是.....
  • Huumm... 很好 :D 我会看看它,但我想现在该代码有什么问题呵呵。我还必须确保 EL 4.1 在 Mono 中工作:P
  • 你为什么要设定1/2的生存时间?为什么不使用用户传入的内容?

标签: c# .net multithreading synchronization readerwriterlockslim


【解决方案1】:

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

  _timer = new Timer((minutesTTL * 60000) / 2); 

【讨论】:

  • 1 * 60000 是一分钟包含的毫秒数,不是吗?我将计时器设置为生存时间的一半。
  • 对,你可以把它除以...我只是想知道你为什么要这样做?
  • 嗯...可能不是正确的方法:P 我只是想确保尽可能少地保留过时的项目。
  • 你明白我在说什么关于 Timer 类吗?为什么它每秒都在发射?
  • 我想告诉你计时器没有设置为每三十秒触发一次。
猜你喜欢
  • 1970-01-01
  • 2012-07-11
  • 1970-01-01
  • 2017-10-15
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-25
相关资源
最近更新 更多