1

Is it important that I lock around the queue?

public abstract class ExpiringCache<TKey,TValue> : IDisposable 
{
    protected readonly object locker = new object();

    protected readonly Dictionary<TKey, TValue> cache =
        new Dictionary<TKey, TValue>();

    private readonly Queue<KeyValuePair<TKey, long>> queue =
        new Queue<KeyValuePair<TKey, long>>();

    private volatile bool dispose;

    private readonly Thread cleaningThread;

    private readonly long lifeSpanTicks;

    public ExpiringCache(TimeSpan lifeSpan)
    {
        // Validate lifeSpan

        if (lifeSpan.Ticks == 0)
        {
            throw new ArgumentOutOfRangeException
                ("lifeSpan", "Must be greater than zero.");
        }

        this.lifeSpanTicks = lifeSpan.Ticks;

        // Begin expiring expired items

        this.cleaningThread = new Thread(() =>
        {
            while (!this.dispose)
            {
                this.RemoveExpired();
                Thread.Sleep(1);
            }
        });

        this.cleaningThread.Start();
    }

    private void RemoveExpired()
    {
        if (this.queue.Count == 0)
        {
            return;
        }

        var pair = this.queue.Peek();

        if (pair.Value >= DateTime.Now.Ticks)
        {
            lock (this.locker)
            {
                this.cache.Remove(pair.Key);
            }

            this.queue.Dequeue();
        }
    }

    public bool Contains(TKey key)
    {
        lock (this.locker)
        {
            return this.cache.ContainsKey(key);
        }
    }

    public void Add(TKey key, TValue value)
    {
        lock (this.locker)
        {
            this.cache.Add(key, value);
        }

        this.queue.Enqueue(new KeyValuePair<TKey, long>
            (key, DateTime.Now.Ticks + this.lifeSpanTicks));
    }

    public void Dispose()
    {
        this.dispose = true;
    }
}
4
  • Have you considered using WeakReferences to let the GC handle object death? Commented May 12, 2009 at 2:38
  • Is anything ever retrieved from the cache? Commented May 12, 2009 at 3:35
  • 1
    About the missing Get() method, we should just suppose that there are more methods than this, to retrieve items, but the OP didn't think it was useful to show them. Or they are used in a subclass, because they are declared protected. Either way, it's not part of the problem. Commented May 12, 2009 at 8:47
  • 1
    @DonkeyMaster I disagree, if this is a code review, then all the code and how the class is meant to be used and derived from is of importance. Commented May 12, 2009 at 21:33

2 Answers 2

6

It's not enough to lock on just the Contains method, for example. See this:
http://blogs.msdn.com/jaredpar/archive/2009/02/16/a-more-usable-thread-safe-collection.aspx

Basically, you need re-think the classic Queue API and use methods like DequeueIfContains() rather than a simple Contains() + Dequeue(), so that the same lock applies both to the Contains check and the Dequeue operation.

Sign up to request clarification or add additional context in comments.

1 Comment

Joel while I don't disagree with what you say, the queue appears to be only for cleanup. There is no Get() method in the code to retrieve items from the cache, just a Contains() to see if they're in the cache.
0

You could use: ConcurrentDictionary<TKey, TValue> and ConcurrentQueue<T>. They are built into C# .Net 4.0 onwards.

1 Comment

From .Net 4.0 onward

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.