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

Rub*_*ink 11 .net c# asynchronous task lazy-evaluation

更新:@usr指出我错误地认为Lazy<T>默认的线程安全模式是LazyThreadSafetyMode.PublicationOnly......

我想懒洋洋地通过async工厂方法计算一个值(即它返回Task<T>)并在成功时缓存它.在例外情况下,我想让我可以使用它.我不然而,要堕入了异常缓存的行为Lazy<T>在其默认模式(LazyThreadSafetyMode.ExecutionAndPublication)

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

Stephen Toub的AsyncLazy课程和写作似乎恰到好处:

public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<Task<T>> taskFactory) :
        base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
    { }

    public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
Run Code Online (Sandbox Code Playgroud)

然而,这实际上与默认行为相同Lazy<T>- 如果出现问题,则不会重试.

我正在寻找一个Task<T>兼容的等价物Lazy<T>(Func<T>, LazyThreadSafetyMode.PublicationOnly),即它应该按照指定的行为: -

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

重要

指定PublicationOnly时,即使指定了工厂方法,也不会缓存异常.

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

Luk*_*keH 3

这是否接近您的要求?

该行为介于ExecutionAndPublication和之间PublicationOnly

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

public sealed class TooLazy<T>
{
    private readonly object _lock = new object();
    private readonly Func<Task<T>> _factory;
    private Task<T> _cached;

    public TooLazy(Func<Task<T>> factory)
    {
        if (factory == null) throw new ArgumentNullException("factory");
        _factory = factory;
    }

    public Task<T> Value
    {
        get
        {
            lock (_lock)
            {
                if ((_cached == null) ||
                    (_cached.IsCompleted && (_cached.Status != TaskStatus.RanToCompletion)))
                {
                    _cached = Task.Run(_factory);
                }
                return _cached;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)