Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can(should?) Lazy<T> be used as a caching technique?

I'd like to use .NET's Lazy<T> class to implement thread safe caching. Suppose we had the following setup:

class Foo
{
    Lazy<string> cachedAttribute;

    Foo()
    {
        invalidateCache();
    }

    string initCache()
    {
        string returnVal = "";
        //CALCULATE RETURNVAL HERE
        return returnVal;
    }

    public String CachedAttr
    {
        get
        {
            return cachedAttribute.Value;
        }
    }

    void invalidateCache()
    {
        cachedAttribute = new Lazy<string>(initCache, true);
    }
}

My questions are:

  1. Would this work at all?
  2. How would the locking have to work?

I feel like I'm missing a lock somewhere near the invalidateCache, but for the life of me I can't figure out what it is.

I'm sure there's a problem with this somewhere, I just haven't figured out where.

[EDIT]

Ok, well it looks like I was right, there were things I hadn't thought about. If a thread sees an outdated cache it'd be a very bad thing, so it looks like "Lazy" is not safe enough. The Property is accessed a lot though, so I was engaging in pre-mature optimization in hopes that I could learn something and have a pattern to use in the future for thread-safe caching. I'll keep working on it.

P.S.: I decided to make the object thread-un-safe and have access to the object be carefully controlled instead.

like image 746
Chris Pfohl Avatar asked Dec 03 '10 17:12

Chris Pfohl


2 Answers

Well, it's not thread-safe in that one thread could still see the old value after another thread sees the new value after invalidation - because the first thread could have not seen the change to cachedAttribute. In theory, that situation could perpetuate forever, although it's pretty unlikely :)

Using Lazy<T> as a cache of unchanging values seems like a better idea to me - more in line with how it was intended - but if you can cope with the possibility of using an old "invalidated" value for an arbitrarily long period in another thread, I think this would be okay.

like image 178
Jon Skeet Avatar answered Oct 24 '22 20:10

Jon Skeet


cachedAttribute is a shared resource that needs to be protected from concurrent modification.

Protect it with a lock:

private readonly object gate = new object();

public string CachedAttr
{
    get
    {
        Lazy<string> lazy;
        lock (gate)                         // 1. Lock
        {
            lazy = this.cachedAttribute;    // 2. Get current Lazy<string>
        }                                   // 3. Unlock
        return lazy.Value                   // 4. Get value of Lazy<string>
                                            //    outside lock
    }
}

void InvalidateCache()
{
    lock (gate)                             // 1. Lock
    {                                       // 2. Assign new Lazy<string>
        cachedAttribute = new Lazy<string>(initCache, true);
    }                                       // 3. Unlock
}

or use Interlocked.Exchange:

void InvalidateCache()
{
    Interlocked.Exchange(ref cachedAttribute, new Lazy<string>(initCache, true));
}

volatile might work as well in this scenario, but it makes my head hurt.

like image 33
dtb Avatar answered Oct 24 '22 20:10

dtb