I have an object that is fairly expensive to create and has a tendency to get created multiple times because each distinct user may have one or many instances of this object. This object can expire as its internal authentication becomes stale and no longer valid.
Because of this, I want need to keep a dictionary so I can keep a list of objects for each user.... but....I need this dictionary to not retain objects indefinitely since they can expire. ( Managing the expiration is outside the scope of this )
I used this article as a starting point, but took the concept a little further to manage the number of objects stored internally to keep the pool from getting too large.
public sealed class PooledDictionary<TKey, TValue>
{
private readonly byte m_poolSize;
private readonly ConcurrentDictionary<TKey, ConcurrentQueue<TValue>> m_pool;
private readonly ConcurrentQueue<TKey> m_keyQueue;
public PooledDictionary(byte maxPoolSize)
{
if (maxPoolSize <= 0)
maxPoolSize = (byte)Math.Min(Environment.ProcessorCount, byte.MaxValue);
m_poolSize = maxPoolSize;
m_keyQueue = new ConcurrentQueue<TKey>();
m_pool = new ConcurrentDictionary<TKey, ConcurrentQueue<TValue>>(Environment.ProcessorCount * 2, m_poolSize);
}
public bool TryAdd(TKey key, TValue value)
{
if (key == null)
return false;
if (!m_pool.ContainsKey(key) && m_pool.TryAdd(key, new ConcurrentQueue<TValue>()))
{
m_keyQueue.Enqueue(key);
while (m_pool.Count > m_poolSize)
{
TKey localKey;
if (m_keyQueue.TryDequeue(out localKey))
{
TryRemove(localKey);
}
}
}
ConcurrentQueue<TValue> q;
if (m_pool.TryGetValue(key, out q))
{
q.Enqueue(value);
while (q.Count > m_poolSize)
{
TValue localValue;
q.TryDequeue(out localValue);
}
}
else
{
return false;
}
return true;
}
public bool TryRemove(TKey key)
{
if (key == null)
return false;
ConcurrentQueue<TValue> value;
return m_pool.TryRemove(key, out value);
}
public int Count(TKey key)
{
if (key == null)
return 0;
if (!m_pool.ContainsKey(key)) m_pool.TryAdd(key, new ConcurrentQueue<TValue>());
ConcurrentQueue<TValue> q;
if (m_pool.TryGetValue(key, out q))
{
return q.Count;
}
return 0;
}
public TValue Request(TKey key, Func<TValue> creator = null)
{
if (key == null)
return default(TValue);
if (!m_pool.ContainsKey(key)) m_pool.TryAdd(key, new ConcurrentQueue<TValue>());
ConcurrentQueue<TValue> q;
if (m_pool.TryGetValue(key, out q))
{
TValue v;
if (q.TryDequeue(out v)) return v;
if (null != creator) return creator();
}
return default(TValue);
}
public bool Release(TKey key, TValue value)
{
return TryAdd(key, value); //just adds it back to key's queue
}
}