I need a thread safe cache to store the instances of a disposable class.
- It will be used with .NET 4.0
- Cache should be aware of if a stored instance is beign used or not.
- When an instance is wanted from the cache, it should look at the stored avaliable instances and give one; if there is no available, create a new instance and store it.
- If the cache has not been used for a period of time, cache should dispose the stored, not being used insances and clear them.
This is the solution I wrote:
private class cache<T> where T : IDisposable
{
Func<T> _createFunc;
long livingTicks;
int livingMillisecs;
public cache(Func<T> createFunc, int livingTimeInSec)
{
this.livingTicks = livingTimeInSec * 10000000;
this.livingMillisecs = livingTimeInSec * 1000;
this._createFunc = createFunc;
}
Stack<T> st = new Stack<T>();
public IDisposable BeginUseBlock(out T item)
{
this.actionOccured();
if (st.Count == 0)
item = _createFunc();
else
lock (st)
if (st.Count == 0)
item = _createFunc();
else
item = st.Pop();
return new blockDisposer(this, item);
}
long _lastTicks;
bool _called;
private void actionOccured()
{
if (!_called)
lock (st)
if (!_called)
{
_called = true;
System.Threading.Timer timer = null;
timer = new System.Threading.Timer((obj) =>
{
if ((DateTime.UtcNow.Ticks - _lastTicks) > livingTicks)
{
timer.Dispose();
this.free();
}
},
null, livingMillisecs, livingMillisecs);
}
_lastTicks = DateTime.UtcNow.Ticks;
}
private void free()
{
lock (st)
{
while (st.Count > 0)
st.Pop().Dispose();
_called = false;
}
}
private class blockDisposer : IDisposable
{
T _item;
cache<T> _c;
public blockDisposer(cache<T> c, T item)
{
this._c = c;
this._item = item;
}
public void Dispose()
{
this._c.actionOccured();
lock (this._c.st)
this._c.st.Push(_item);
}
}
}
This is a sample use:
class MyClass:IDisposable
{
public MyClass()
{
//expensive work
}
public void Dispose()
{
//free
}
public void DoSomething(int i)
{
}
}
private static Lazy<cache<MyClass>> myCache = new Lazy<cache<MyClass>>(() => new cache<MyClass>(() => new MyClass(), 60), true);//free 60sec. after last call
private static void test()
{
Parallel.For(0, 100000, (i) =>
{
MyClass cl;
using (myCache.Value.BeginUseBlock(out cl))
cl.DoSomething(i);
});
}
My questions:
- Is there a faster way of doing this? (I've searched for the MemoryCache examples, but couln't figure out how I could use it for my requirements. And it requires a key check. Stack.Pop would be faster than a key search, I thought; and for my problem, performance is very important.)
- In order to dispose the instances after a while (60sec. for the example code) I had to use a Timer. I just need a delayed function call that would be re-delayed on each action happening with the cache. Is there a way to do that without using a timer?
Edit: I've tried @mjwills's comment. The performance is better with this:
ConcurrentStack<T> st = new ConcurrentStack<T>();
public IDisposable BeginUseBlock(out T item)
{
this.actionOccured();
if (!st.TryPop(out item))
item = _createFunc();
return new blockDisposer(this, item);
}
Edit2: In my cas its not required, but if we need to control the size of the stack and dispose the unused objects, using a separate counter which will be increment-decremented with Interlocked.Increment will be faster (@mjwills)