如果它没有抛出,则从工厂方法缓存结果

更新:@usr指出我错误地认为Lazy的默认线程安全模式是LazyThreadSafetyMode.PublicationOnly …后严重修改…

我想懒惰地通过async工厂方法计算一个值(即它返回Task )并在成功时缓存它。 在例外情况下,我想让我可以使用它。 但是,我不想成为Lazy在其默认模式下具有的exception缓存行为 ( LazyThreadSafetyMode.ExecutionAndPublication

exception缓存:使用工厂方法时,会缓存exception。 也就是说,如果工厂方法在线程第一次尝试访问Lazy对象的Value属性时抛出exception,则每次后续尝试都会抛出相同的exception。 这确保了对Value属性的每次调用都会产生相同的结果,并避免在不同的线程获得不同结果时可能出现的细微错误。 懒惰代表一个实际的T,否则它将在某个早期点(通常在启动期间)初始化。 在那个早期点失败通常是致命的。 如果存在可恢复故障的可能性,我们建议您将重试逻辑构建到初始化例程(在本例中为工厂方法),就像您没有使用延迟初始化一样。

Stephen Toub有一个AsyncLazy类和AsyncLazy似乎恰到好处:

 public class AsyncLazy : Lazy<Task> { public AsyncLazy(Func<Task> taskFactory) : base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) { } public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); } } 

然而,这与默认的Lazy实际上是相同的行为 – 如果出现问题,则不会重试。

我正在寻找与Lazy(Func, LazyThreadSafetyMode.PublicationOnly)兼容的Task ,即它应该按照指定的方式运行: –

锁定的替代方法在某些情况下,您可能希望避免Lazy对象的默认锁定行为的开销。 在极少数情况下,可能存在死锁的可能性。 在这种情况下,您可以使用Lazy(LazyThreadSafetyMode)或Lazy(Func,LazyThreadSafetyMode)构造函数,并指定LazyThreadSafetyMode.PublicationOnly。 如果线程同时调用Value属性,这使Lazy对象能够在多个线程中的每个线程上创建一个延迟初始化对象的副本。 Lazy对象确保所有线程使用延迟初始化对象的相同实例并丢弃未使用的实例。 因此,降低锁定开销的成本是您的程序有时可能会创建并丢弃昂贵对象的额外副本。 在大多数情况下,这不太可能。 Lazy(LazyThreadSafetyMode)和Lazy(Func,LazyThreadSafetyMode)构造函数的示例演示了此行为。

重要

指定PublicationOnly时,即使指定了工厂方法,也不会缓存exception。

是否有任何FCL, Nito.AsyncEx或类似的构造可能很适合这里? 如果没有这个,任何人都可以看到一种优雅的方式来控制“尝试进行中”位(我很好,每个调用者都以与Lazy(相同的方式进行自己的尝试Lazy( …, (LazyThreadSafetyMode.PublicationOnly)并且仍然有,并且缓存管理整齐地封装?

这可以满足您的要求吗?

该行为介于ExecutionAndPublicationPublicationOnly之间。

当初始化程序正在进行中时,对Value所有调用都将被传递给相同的任务(临时缓存但随后可能成功或失败); 如果初始化程序成功,那么已完成的任务将永久缓存; 如果初始化程序失败,那么下一次调用Value将创建一个全新的初始化任务,并且该过程再次开始!

 public sealed class TooLazy { private readonly object _lock = new object(); private readonly Func> _factory; private Task _cached; public TooLazy(Func> factory) { if (factory == null) throw new ArgumentNullException("factory"); _factory = factory; } public Task Value { get { lock (_lock) { if ((_cached == null) || (_cached.IsCompleted && (_cached.Status != TaskStatus.RanToCompletion))) { _cached = Task.Run(_factory); } return _cached; } } } } 

免责声明:这是重构Lazy的疯狂尝试。 它绝不是生产等级代码。

我冒昧地查看了Lazy源代码并稍微修改它以使用Func> 。 我重构了Value属性以成为FetchValueAsync方法,因为我们无法在属性中等待。 您可以使用Task.Result来阻止async操作,因此您仍然可以使用Value属性,我不想这样做,因为它可能会导致问题。 所以它有点麻烦,但仍然有效。 此代码未经过完全测试:

 public class AsyncLazy { static class LazyHelpers { internal static readonly object PUBLICATION_ONLY_SENTINEL = new object(); } class Boxed { internal Boxed(T value) { this.value = value; } internal readonly T value; } class LazyInternalExceptionHolder { internal ExceptionDispatchInfo m_edi; internal LazyInternalExceptionHolder(Exception ex) { m_edi = ExceptionDispatchInfo.Capture(ex); } } static readonly Func> alreadyInvokedSentinel = delegate { Contract.Assert(false, "alreadyInvokedSentinel should never be invoked."); return default(Task); }; private object boxed; [NonSerialized] private Func> valueFactory; [NonSerialized] private object threadSafeObj; public AsyncLazy() : this(LazyThreadSafetyMode.ExecutionAndPublication) { } public AsyncLazy(Func> valueFactory) : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication) { } public AsyncLazy(bool isThreadSafe) : this(isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None) { } public AsyncLazy(LazyThreadSafetyMode mode) { threadSafeObj = GetObjectFromMode(mode); } public AsyncLazy(Func> valueFactory, bool isThreadSafe) : this(valueFactory, isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None) { } public AsyncLazy(Func> valueFactory, LazyThreadSafetyMode mode) { if (valueFactory == null) throw new ArgumentNullException("valueFactory"); threadSafeObj = GetObjectFromMode(mode); this.valueFactory = valueFactory; } private static object GetObjectFromMode(LazyThreadSafetyMode mode) { if (mode == LazyThreadSafetyMode.ExecutionAndPublication) return new object(); if (mode == LazyThreadSafetyMode.PublicationOnly) return LazyHelpers.PUBLICATION_ONLY_SENTINEL; if (mode != LazyThreadSafetyMode.None) throw new ArgumentOutOfRangeException("mode"); return null; // None mode } public override string ToString() { return IsValueCreated ? ((Boxed) boxed).value.ToString() : "NoValue"; } internal LazyThreadSafetyMode Mode { get { if (threadSafeObj == null) return LazyThreadSafetyMode.None; if (threadSafeObj == (object)LazyHelpers.PUBLICATION_ONLY_SENTINEL) return LazyThreadSafetyMode.PublicationOnly; return LazyThreadSafetyMode.ExecutionAndPublication; } } internal bool IsValueFaulted { get { return boxed is LazyInternalExceptionHolder; } } public bool IsValueCreated { get { return boxed != null && boxed is Boxed; } } public async Task FetchValueAsync() { Boxed boxed = null; if (this.boxed != null) { // Do a quick check up front for the fast path. boxed = this.boxed as Boxed; if (boxed != null) { return boxed.value; } LazyInternalExceptionHolder exc = this.boxed as LazyInternalExceptionHolder; exc.m_edi.Throw(); } return await LazyInitValue().ConfigureAwait(false); } ///  /// local helper method to initialize the value ///  /// The inititialized T value private async Task LazyInitValue() { Boxed boxed = null; LazyThreadSafetyMode mode = Mode; if (mode == LazyThreadSafetyMode.None) { boxed = await CreateValue().ConfigureAwait(false); this.boxed = boxed; } else if (mode == LazyThreadSafetyMode.PublicationOnly) { boxed = await CreateValue().ConfigureAwait(false); if (boxed == null || Interlocked.CompareExchange(ref this.boxed, boxed, null) != null) { boxed = (Boxed)this.boxed; } else { valueFactory = alreadyInvokedSentinel; } } else { object threadSafeObject = Volatile.Read(ref threadSafeObj); bool lockTaken = false; try { if (threadSafeObject != (object)alreadyInvokedSentinel) Monitor.Enter(threadSafeObject, ref lockTaken); else Contract.Assert(this.boxed != null); if (this.boxed == null) { boxed = await CreateValue().ConfigureAwait(false); this.boxed = boxed; Volatile.Write(ref threadSafeObj, alreadyInvokedSentinel); } else { boxed = this.boxed as Boxed; if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder { LazyInternalExceptionHolder exHolder = this.boxed as LazyInternalExceptionHolder; Contract.Assert(exHolder != null); exHolder.m_edi.Throw(); } } } finally { if (lockTaken) Monitor.Exit(threadSafeObject); } } Contract.Assert(boxed != null); return boxed.value; } /// Creates an instance of T using valueFactory in case its not null or use reflection to create a new T() /// An instance of Boxed. private async Task CreateValue() { Boxed localBoxed = null; LazyThreadSafetyMode mode = Mode; if (valueFactory != null) { try { // check for recursion if (mode != LazyThreadSafetyMode.PublicationOnly && valueFactory == alreadyInvokedSentinel) throw new InvalidOperationException("Recursive call to Value property"); Func> factory = valueFactory; if (mode != LazyThreadSafetyMode.PublicationOnly) // only detect recursion on None and ExecutionAndPublication modes { valueFactory = alreadyInvokedSentinel; } else if (factory == alreadyInvokedSentinel) { // Another thread ----d with us and beat us to successfully invoke the factory. return null; } localBoxed = new Boxed(await factory().ConfigureAwait(false)); } catch (Exception ex) { if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode boxed = new LazyInternalExceptionHolder(ex); throw; } } else { try { localBoxed = new Boxed((T)Activator.CreateInstance(typeof(T))); } catch (MissingMethodException) { Exception ex = new MissingMemberException("Missing parametersless constructor"); if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode boxed = new LazyInternalExceptionHolder(ex); throw ex; } } return localBoxed; } } 

现在,我正在使用这个:

 public class CachedAsync { readonly Func> _taskFactory; T _value; public CachedAsync(Func> taskFactory) { _taskFactory = taskFactory; } public TaskAwaiter GetAwaiter() { return Fetch().GetAwaiter(); } async Task Fetch() { if (_value == null) _value = await _taskFactory(); return _value; } } 

虽然它适用于我的场景(我没有多个触发线程等),但它并不优雅,也不提供线程安全协调

  • la LazyThreadSafetyMode.ExecutionAndPublication OR进行单次尝试
  • > = 1成功之后的稳定结果la LazyThreadSafetyMode.PublicationOnly

我根据@ LukeH的答案使用的版本。 请注意,不是这个。

 // http://stackoverflow.com/a/33872589/11635 public class LazyTask { public static LazyTask Create(Func> factory) { return new LazyTask(factory); } } ///  /// Implements a caching/provisioning model we can term LazyThreadSafetyMode.ExecutionAndPublicationWithoutFailureCaching /// - Ensures only a single provisioning attempt in progress /// - a successful result gets locked in /// - a failed result triggers replacement by the first caller through the gate to observe the failed state /// ///  /// Inspired by Stephen Toub http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/asynclazy-lt-t-gt.aspx /// Implemented with sensible semantics by @LukeH via SO http://stackoverflow.com/a/33942013/11635 ///  public class LazyTask { readonly object _lock = new object(); readonly Func> _factory; Task _cached; public LazyTask(Func> factory) { if (factory == null) throw new ArgumentNullException("factory"); _factory = factory; } ///  /// Allow await keyword to be applied directly as if it was a Task. See Value for semantics. ///  public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); } ///  /// Trigger a load attempt. If there is an attempt in progress, take that. If preceding attempt failed, trigger a retry. ///  public Task Value { get { lock (_lock) if (_cached == null || BuildHasCompletedButNotSucceeded()) _cached = _factory(); return _cached; } } bool BuildHasCompletedButNotSucceeded() { return _cached.IsCompleted && _cached.Status != TaskStatus.RanToCompletion; } }