CSharp · Console
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}");
}
}