private static object syncRoot = new object(); private static object value = null; public static object Value { get { if (value == null) { lock (syncRoot) { if (value == null) { // Only one concurrent thread will attempt to create the underlying value. // And if `GetTheValueFromSomewhere` throws an exception, then the value field // will not be assigned to anything and later access // to the Value property will retry. As far as the exception // is concerned it will obviously be propagated // to the consumer of the Value getter value = GetTheValueFromSomewhere(); } } } return value; } }
更新:
为了满足您对传播到所有等待读者线程的相同exception的要求:
private static Lazy lazy = new Lazy(GetTheValueFromSomewhere); public static object Value { get { try { return lazy.Value; } catch { // We recreate the lazy field so that subsequent readers // don't just get a cached exception but rather attempt // to call the GetTheValueFromSomewhere() expensive method // in order to calculate the value again lazy = new Lazy(GetTheValueFromSomewhere); // Re-throw the exception so that all blocked reader threads // will get this exact same exception thrown. throw; } } }
private static Task _fetcher = null; private static object _value = null; public static object Value { get { if (_value != null) return _value; //We're "locking" then var tcs = new TaskCompletionSource(); var tsk = Interlocked.CompareExchange(ref _fetcher, tcs.Task, null); if (tsk == null) //We won the race to set up the task { try { var result = new object(); //Whatever the real, expensive operation is tcs.SetResult(result); _value = result; return result; } catch (Exception ex) { Interlocked.Exchange(ref _fetcher, null); //We failed. Let someone else try again in the future tcs.SetException(ex); throw; } } tsk.Wait(); //Someone else is doing the work return tsk.Result; } }
我有点担心 – 有人能看到任何明显的比赛,它会以一种不明显的方式失败吗?
这样的事情可能会有所帮助:
using System; using System.Threading; namespace ADifferentLazy { /// /// Basically the same as Lazy with LazyThreadSafetyMode of ExecutionAndPublication, BUT exceptions are not cached /// public class LazyWithNoExceptionCaching { private Func valueFactory; private T value = default(T); private readonly object lockObject = new object(); private bool initialized = false; private static readonly Func ALREADY_INVOKED_SENTINEL = () => default(T); public LazyWithNoExceptionCaching(Func valueFactory) { this.valueFactory = valueFactory; } public bool IsValueCreated { get { return initialized; } } public T Value { get { //Mimic LazyInitializer.EnsureInitialized()'s double-checked locking, whilst allowing control flow to clear valueFactory on successful initialisation if (Volatile.Read(ref initialized)) return value; lock (lockObject) { if (Volatile.Read(ref initialized)) return value; value = valueFactory(); Volatile.Write(ref initialized, true); } valueFactory = ALREADY_INVOKED_SENTINEL; return value; } } } }