.NET Fiddle

CSharp · Console

/Program.cs

using System;
using System.Linq;
using System.Threading;

public class Program
{
    public static void Main()
    {
        int counter = 0;
        //var lazy = new Lazy<int>(() =>
        //var lazy = new AtomicLazy<int>(() =>
        //var lazy = new SimpleLazy<int>(() =>
        var lazy = new LazyWithRetry<int>(() =>
        {
            Print($"**Value factory invoked");
            var value = Interlocked.Increment(ref counter);
            Thread.Sleep(250);
            if (value <= 2) throw new ApplicationException($"Oops! ({value})");
            return value;
        });
        var workers = Enumerable.Range(1, 10).Select(n => new Thread(() =>
        {
            Thread.Sleep(100 * n);
            Print($"Worker #{n} before requesting value");
            try
            {
                var value = lazy.Value;
                Print($"--Worker #{n} received value: {value}");
            }
            catch (Exception ex)
            {
                Print($"--Worker #{n} failed: {ex.Message}");
            }
        })).ToList();
        workers.ForEach(w => w.Start());
        workers.ForEach(w => w.Join());
        Print($"Finished");
    }

    /// <summary>
    /// Represents the result of an action that is invoked lazily on demand, and can be
    /// retried as many times as needed until it succeeds, while enforcing a
    /// non-overlapping execution policy.
    /// </summary>
    /// <remarks>
    /// In case the action is successful, it is never invoked again. In case of failure
    /// the error is propagated to the invoking thread, as well as to any other threads
    /// that are currently waiting for the result. The error in not cached. The action
    /// will be invoked again when the next thread requests the result, repeating the
    /// same pattern.
    /// </remarks>
    public class LazyWithRetry<T>
    {
        private volatile Lazy<T> _lazy;

        public LazyWithRetry(Func<T> valueFactory)
        {
            ArgumentNullException.ThrowIfNull(valueFactory);
            Func<T> valueFactoryWrapper = null;
            valueFactoryWrapper = () =>
            {
                try { return valueFactory(); }
                catch { _lazy = new Lazy<T>(valueFactoryWrapper); throw; }
            };
            _lazy = new Lazy<T>(valueFactoryWrapper);
        }

        public T Value => _lazy.Value;
    }

    // vernou / Marius Gundersen
    public class AtomicLazy<T>
    {
        private readonly Func<T> _valueFactory;
        private T _value;
        private bool _initialized;
        private object _lock;

        public AtomicLazy(Func<T> valueFactory)
        {
            _valueFactory = valueFactory;
        }

        public T Value => LazyInitializer
            .EnsureInitialized(ref _value, ref _initialized, ref _lock, _valueFactory);
    }

    // tsul (modified to allow T to be value type)
    public class SimpleLazy<T>
    {
        private readonly object _locker = new object();
        private readonly Func<T> _valueFactory;
        private (T Value, bool HasValue) _cached;

        public SimpleLazy(Func<T> valueFactory)
        {
            _valueFactory = valueFactory;
        }

        public T Value
        {
            get
            {
                lock (_locker)
                {
                    if (!_cached.HasValue)
                        _cached = (_valueFactory(), true);
                    return _cached.Value;
                }
            }
        }
    }

    static void Print(object value)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} {($"[{Thread.CurrentThread.ManagedThreadId}]"),4} > {value}");
    }
}
;