Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy<T> with expiration time

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 passed, then we reexecute the function and reset expiration time.

I'm not familiar with extensions, partial keyword, and I don't know the best way to do that.

Thanks

EDIT :

The code so far :

NEW EDIT :

the new code :

public class LazyWithExpiration<T>
{
    private volatile bool expired;
    private TimeSpan expirationTime;
    private Func<T> func;
    private Lazy<T> lazyObject;

    public LazyWithExpiration( Func<T> func, TimeSpan expirationTime )
    {
        this.expirationTime = expirationTime;
        this.func = func;

        Reset();
    }

    public void Reset()
    {
        lazyObject = new Lazy<T>( func );
        expired = false;
    }

    public T Value
    {
        get
        {
            if ( expired )
                Reset();

            if ( !lazyObject.IsValueCreated )
            {
                Task.Factory.StartNew( () =>
                {
                    Thread.Sleep( expirationTime );
                    expired = true;
                } );
            }

            return lazyObject.Value;
        }
    }

}
like image 568
Bastiflew Avatar asked Sep 08 '13 14:09

Bastiflew


3 Answers

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();
            }
        }
    }
}
like image 111
Pietro Avatar answered Oct 21 '22 06:10

Pietro


2021 edit:

You probably shouldn't use this code. Maybe it's good enough for a very simple app, but it has some issues. It doesn't support cache invalidation, it may have issues related to DST or other time zone changes due to the way it uses DateTime, and it uses locks in a way that's safe but potentially quite slow. Consider using something like MemoryCache instead.

Original answer:

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;
            }
        }
    }
}
like image 25
Magnus Grindal Bakken Avatar answered Oct 21 '22 04:10

Magnus Grindal Bakken


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.

like image 43
Mario Avatar answered Oct 21 '22 04:10

Mario