从 [n async] 工厂方法缓存结果,如果它不抛出 [英] caching the result from a [n async] factory method iff it doesn't throw

查看:13
本文介绍了从 [n async] 工厂方法缓存结果,如果它不抛出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我想通过 async 工厂方法懒惰地计算一个值(即它返回 Task)并在成功时缓存它.作为例外,我希望它可供我使用.但是,我不想成为 Lazy 在其默认模式下的异常缓存行为 (LazyThreadSafetyMode.ExecutionAndPublication)

<块引用>

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

Stephen Toub 有一个 AsyncLazy 类和 writeup 看起来恰到好处:

公共类 AsyncLazy: Lazy{public AsyncLazy(Func> taskFactory):base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()){ }公共 TaskAwaiter<T>GetAwaiter() { 返回值.GetAwaiter();}}

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

我正在寻找与 Lazy(Func, LazyThreadSafetyMode.PublicationOnly) 等效的 Task 兼容等效项,即它的行为应为指定:-

<块引用>

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

重要

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

是否有任何 FCL、Nito.AsyncEx 或类似的结构可以很好地适应这里?如果做不到这一点,任何人都可以看到一种优雅的方式来控制正在进行的尝试"吗?位(我对每个调用者都以与 Lazy<T>( ..., (LazyThreadSafetyMode.PublicationOnly) 相同的方式进行自己的尝试)和仍然把它和缓存管理封装得很好吗?

解决方案

这是否符合您的要求?

行为介于 ExecutionAndPublicationPublicationOnly 之间.

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

公共密封类 TooLazy{私有只读对象_lock = new object();private readonly Func>_工厂;私人任务<T>_缓存;公共工具懒惰(Func> factory){if (factory == null) throw new ArgumentNullException("factory");_factory = 工厂;}公共任务<T>价值{得到{锁 (_lock){if ((_cached == null) ||(_cached.IsCompleted && (_cached.Status != TaskStatus.RanToCompletion))){_cached = Task.Run(_factory);}返回_缓存;}}}}

UPDATE: Heavily revised after @usr pointed out I'd incorrectly assumed Lazy<T>'s default thread safety mode was LazyThreadSafetyMode.PublicationOnly...

I want to lazily compute a value via an async Factory Method (i.e. it returns Task<T>) and have it cached upon success. On exception, I want to have that be available to me. I do not however, want to fall prey to the exception caching behavior that Lazy<T> has in its default mode (LazyThreadSafetyMode.ExecutionAndPublication)

Exception caching: When you use factory methods, exceptions are cached. That is, if the factory method throws an exception the first time a thread tries to access the Value property of the Lazy object, the same exception is thrown on every subsequent attempt. This ensures that every call to the Value property produces the same result and avoids subtle errors that might arise if different threads get different results. The Lazy stands in for an actual T that otherwise would have been initialized at some earlier point, usually during startup. A failure at that earlier point is usually fatal. If there is a potential for a recoverable failure, we recommend that you build the retry logic into the initialization routine (in this case, the factory method), just as you would if you weren’t using lazy initialization.

Stephen Toub has an AsyncLazy class and writeup that seems just right:

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(); }
}

however that's effectively the same behavior as a default Lazy<T> - if there's a problem, there will be no retries.

I'm looking for a Task<T> compatible equivalent of Lazy<T>(Func<T>, LazyThreadSafetyMode.PublicationOnly), i.e. it should behave as that is specified:-

Alternative to locking In certain situations, you might want to avoid the overhead of the Lazy object's default locking behavior. In rare situations, there might be a potential for deadlocks. In such cases, you can use the Lazy(LazyThreadSafetyMode) or Lazy(Func, LazyThreadSafetyMode) constructor, and specify LazyThreadSafetyMode.PublicationOnly. This enables the Lazy object to create a copy of the lazily initialized object on each of several threads if the threads call the Value property simultaneously. The Lazy object ensures that all threads use the same instance of the lazily initialized object and discards the instances that are not used. Thus, the cost of reducing the locking overhead is that your program might sometimes create and discard extra copies of an expensive object. In most cases, this is unlikely. The examples for the Lazy(LazyThreadSafetyMode) and Lazy(Func, LazyThreadSafetyMode) constructors demonstrate this behavior.

IMPORTANT

When you specify PublicationOnly, exceptions are never cached, even if you specify a factory method.

Is there any FCL, Nito.AsyncEx or similar construct that might fit in nicely here? Failing this, can anyone see an elegant way to gate the "attempt in progress" bit (I'm OK with each caller making its own attempt in the same way that a Lazy<T>( ..., (LazyThreadSafetyMode.PublicationOnly) does) and yet still have that and the cache management encapsulated neatly?

解决方案

Does this get anywhere near your requirements?

The behaviour falls somewhere between ExecutionAndPublication and PublicationOnly.

While the initializer is in-flight all calls to Value will be handed the same task (which is cached temporarily but could subsequently succeed or fail); if the initializer succeeds then that completed task is cached permanently; if the initializer fails then the next call to Value will create a completely new initialization task and the process begins again!

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;
            }
        }
    }
}

这篇关于从 [n async] 工厂方法缓存结果,如果它不抛出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆