Can (should?) Lazy <T> be used as a cache method?
I would like to use the .NET Lazy<T> class to implement secure thread 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:
- Will it even work?
- How should a lock work?
I feel like I don’t have a lock somewhere near invalidateCache, but for life I can’t understand what it is.
I am sure that there is a problem somewhere, I just did not understand where.
[EDIT]
Well, it looks like I was right, there were things that I did not think about. If the stream sees an obsolete cache, it will be very bad, so it looks "Lazy" is not safe enough. There is a lot of property available, though, so I was involved in pre-mature optimization in the hope that I could learn something and have a sample that would be used in the future for streaming caching. I will continue to work on this.
PS: I decided to make the object unsafe and gain access to the object, which will be carefully monitored.
Well, it is not thread safe in that one thread can still see the old value after another thread sees the new value after the invalidation - because the first thread could not see the change to cachedAttribute . Theoretically, this situation can perpetuate forever, although this is quite unlikely :)
Using Lazy<T> as a cache of constant values seems to me a better idea - more in accordance with how it was intended - but if you can cope with the possibility of using the old "invalid" value for an arbitrarily long period in another topic, I think that everything will be fine.
cachedAttribute is a shared resource that must be protected from simultaneous modification.
Protect it with 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 may work in this scenario too, but my head hurts.