Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy<T> without exception caching

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:

  • we want a thread-safe cache
  • the cache is a key-value cache. Let's simplify it and the key is type of string and the value is also type of string
  • producing an item is expensive - thus production must be started by one and only one thread for given key. Production for key "a" doesn't block production for key "b"
  • if production ended in success - we want to cache the produced item
  • if during production exception is thrown - we want to pass the exception to the caller. The caller's responsibility is to decide about retry/giving up/logging. Exception isn't cached - next call to the cache for this item will start the item production.

In my example:

  • we have LightsabersCache, LightsabersCache.GetLightsaber method gets the value for given key
  • LightsaberProvider is only a dummy provider. It mimics production nature: the production is expensive (2 seconds), and sometimes (in this case only first time, for key="2") exception is thrown
  • the program starts 15 threads and each thread tries 10 times to get the value from range <0;4>. Only one time exception is thrown, so only one time we should see "Dark side happened...". There are 5 keys in the range <0;4> so only 5 "Lightsaber produced" messages should be on the console. We should see 6 times the message "LightsaberProvider.GetFor jedi: x" because one time for each key + one failed for key "2".
like image 778
piotrwest Avatar asked Dec 21 '15 10:12

piotrwest


People also ask

Is Lazy T thread safe?

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.

Is lazy initialization good?

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.

What is Lazy <> in C#?

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.


1 Answers

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.

like image 123
tsul Avatar answered Sep 22 '22 05:09

tsul