Is there a System.Lazy<T>
without exception caching? Or another nice solution for lazy multithreading initialization & caching?
I've got following program (fiddle it here):
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
namespace ConsoleApplication3
{
public class Program
{
public class LightsaberProvider
{
private static int _firstTime = 1;
public LightsaberProvider()
{
Console.WriteLine("LightsaberProvider ctor");
}
public string GetFor(string jedi)
{
Console.WriteLine("LightsaberProvider.GetFor jedi: {0}", jedi);
Thread.Sleep(TimeSpan.FromSeconds(1));
if (jedi == "2" && 1 == Interlocked.Exchange(ref _firstTime, 0))
{
throw new Exception("Dark side happened...");
}
Thread.Sleep(TimeSpan.FromSeconds(1));
return string.Format("Lightsaver for: {0}", jedi);
}
}
public class LightsabersCache
{
private readonly LightsaberProvider _lightsaberProvider;
private readonly ConcurrentDictionary<string, Lazy<string>> _producedLightsabers;
public LightsabersCache(LightsaberProvider lightsaberProvider)
{
_lightsaberProvider = lightsaberProvider;
_producedLightsabers = new ConcurrentDictionary<string, Lazy<string>>();
}
public string GetLightsaber(string jedi)
{
Lazy<string> result;
if (!_producedLightsabers.TryGetValue(jedi, out result))
{
result = _producedLightsabers.GetOrAdd(jedi, key => new Lazy<string>(() =>
{
Console.WriteLine("Lazy Enter");
var light = _lightsaberProvider.GetFor(jedi);
Console.WriteLine("Lightsaber produced");
return light;
}, LazyThreadSafetyMode.ExecutionAndPublication));
}
return result.Value;
}
}
public void Main()
{
Test();
Console.WriteLine("Maximum 1 'Dark side happened...' strings on the console there should be. No more, no less.");
Console.WriteLine("Maximum 5 lightsabers produced should be. No more, no less.");
}
private static void Test()
{
var cache = new LightsabersCache(new LightsaberProvider());
Parallel.For(0, 15, t =>
{
for (int i = 0; i < 10; i++)
{
try
{
var result = cache.GetLightsaber((t % 5).ToString());
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Thread.Sleep(25);
}
});
}
}
}
Basically I want to cache produced lightsabers, but producing them is expensive and tricky - sometimes exceptions may happen. I want to allow only one producer at time for given jedi
, but when exception is thrown - I want another producer to try again. Therefore, desired behavior is like System.Lazy<T>
with LazyThreadSafetyMode.ExecutionAndPublication
option, but without exceptions caching.
All in all, following technical requirements must be meet:
In my example:
By default, Lazy<T> objects are thread-safe. That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe.
Lazy initialization is useful when calculating the value of the field is time consuming and you don't want to do it until you actually need the value. So it's often useful in situations where the field isn't needed in many contexts or we want to get the object initialized quickly and prefer any delay to be later on.
System namespace in . NET Framework has Lazy class that provides us with basic mechanism for lazy loads. The idea of Lazy class is simple - initialize class with action that builds or gets lazy loaded value and when value is asked then run the action if lazy loading is not happened yet.
It's hard to use built-in Lazy for that: you should wrap your LazyWithoutExceptionCaching.Value getter in a lock. But that makes the use of the built-in Lazy
redundant: you'll have unnecessary locks inside the Lazy.Value
getter.
It's better to write your own Lazy implementation especially if you intend to instantiate reference types only, it turns to be rather simple:
public class SimpleLazy<T> where T : class
{
private readonly Func<T> valueFactory;
private T instance;
private readonly object locker = new object();
public SimpleLazy(Func<T> valueFactory)
{
this.valueFactory = valueFactory;
this.instance = null;
}
public T Value
{
get
{
lock (locker)
return instance ?? (instance = valueFactory());
}
}
}
P.S. Maybe we'll have this functionality built-in when this issue gets closed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With