用于维护线程安全缓存的类
我正在开发一个线程安全类,我将其用作缓存,它应该可以在.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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我看到的第一个问题(假设您使用 System.Timers.Timer 接受毫秒并且您正在传递秒)。
First issue i see (assuming you are using System.Timers.Timer accepts milliseconds and you are passing seconds).