用于维护线程安全缓存的类

发布于 2024-08-28 01:10:09 字数 5794 浏览 7 评论 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 类中存在问题?

预先感谢,亲切的问候。

I'm developing a thread safe class that I'll use as cache, it should work in .NET and Mono.

The items have a time to live, and every time that a object is retrieved, its time to live is refreshed. Each time that I add a item, the timestamp is added to another collection that holds the same keys. A timer raises the method that looks for out dated items and remove them.

When I try to get and item, I have to provide also a delegate indicating how to obtain it if it wouldn't exist in the cache.

I've testing, and although the items removal should happen every 30 seconds in the test, it's happening very often, almost every second, and I'don't know why.

This is the class:

    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
}

As you can see, in debug mode, when a item is added a "_" symbol is printed, and when and item is removed a "-" symbol is printed. In the tests, after the second minute can see how items are removed and added in the same second, when the items should be removed only every 30 seconds, and I don't know why:

This is how I tests:

        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();
    }

Do you see any problem in the GenericCache class?

Thanks in advance, kind regards.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

ぃ弥猫深巷。 2024-09-04 01:10:09

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

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

First issue i see (assuming you are using System.Timers.Timer accepts milliseconds and you are passing seconds).

  _timer = new Timer((minutesTTL * 60000) / 2); 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文