Я разрабатываю потокобезопасный класс, который буду использовать в качестве кэша, он должен работать в .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?
Заранее спасибо, всего наилучшего.