Lazy with expiration time

前端 未结 3 675
春和景丽
春和景丽 2021-02-13 21:29

I want to implement an expiration time on a Lazy object. The expiration cooldown must start with the first retrieve of the value. If we get the value, and the expiration time is

相关标签:
3条回答
  • 2021-02-13 21:46

    I needed the same thing. But I would prefer an implementation without locked reads when there is no write.

    public class ExpiringLazy<T>
    {
        private readonly Func<T> factory;
        private readonly TimeSpan lifetime;
        private readonly ReaderWriterLockSlim locking = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    
        private T value;
        private DateTime expiresOn = DateTime.MinValue;
    
        public ExpiringLazy(Func<T> factory, TimeSpan lifetime)
        {
            this.factory = factory;
            this.lifetime = lifetime;
        }
    
        public T Value
        {
            get
            {
                DateTime now = DateTime.UtcNow;
                locking.EnterUpgradeableReadLock();
                try
                {
                    if (expiresOn < now)
                    {
                        locking.EnterWriteLock();
                        try
                        {
                            if (expiresOn < now)
                            {
                                value = factory();
                                expiresOn = DateTime.UtcNow.Add(lifetime);
                            }
                        }
                        finally
                        {
                            locking.ExitWriteLock();
                        }
                    }
    
                    return value;
                }
                finally
                {
                    locking.ExitUpgradeableReadLock();
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-13 21:47

    I don't think Lazy<T> would have any influence here, it's more like a general approach, essentially being similar to the singleton pattern.

    You'll need a simple wrapper class which will either return the real object or pass all calls to it.

    I'd try something like this (out of memory, so might include bugs):

    public class Timed<T> where T : new() {
        DateTime init;
        T obj;
    
        public Timed() {
            init = new DateTime(0);
        }
    
        public T get() {
            if (DateTime.Now - init > max_lifetime) {
                obj = new T();
                init = DateTime.Now;
            }
            return obj;
        }
    }
    

    To use, you'd then just use Timed<MyClass> obj = new Timed<MyClass>(); rather than MyClass obj = new MyClass();. And actual calls would be obj.get().doSomething() instead of obj.doSomething().

    Edit:

    Just to note, you won't have to combine an approach similar to mine above with Lazy<T> because you're essentially forcing a delayed initialization already. You could of course define the maximum lifetime in the constructor for example.

    0 讨论(0)
  • 2021-02-13 21:52

    I agree with the other commenters that you probably shouldn't touch Lazy at all. Lazy isn't very complicated if you ignore the multiple thread-safety options, so just implement it from scratch.

    I quite like the idea by the way, although I don't know if I'd be comfortable using it as a general purpose caching strategy. It might be sufficient for some of the simpler scenarios.

    Here's my stab at it. If you don't need it to be thread-safe you can just remove the locking stuff. I don't think it's possible to use the double-checking lock pattern here because of the chance that the cached value may be be invalidated inside the lock.

    public class Temporary<T>
    {
        private readonly Func<T> factory;
        private readonly TimeSpan lifetime;
        private readonly object valueLock = new object();
    
        private T value;
        private bool hasValue;
        private DateTime creationTime;
    
        public Temporary(Func<T> factory, TimeSpan lifetime)
        {
            this.factory = factory;
            this.lifetime = lifetime;
        }
    
        public T Value
        {
            get
            {
                DateTime now = DateTime.Now;
                lock (this.valueLock)
                {
                    if (this.hasValue)
                    {
                        if (this.creationTime.Add(this.lifetime) < now)
                        {
                            this.hasValue = false;
                        }
                    }
    
                    if (!this.hasValue)
                    {
                        this.value = this.factory();
                        this.hasValue = true;
    
                        // You can also use the existing "now" variable here.
                        // It depends on when you want the cache time to start
                        // counting from.
                        this.creationTime = Datetime.Now;
                    }
    
                    return this.value;
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题