Caching a value just once and update it without blocking other threads I need some advice about my code.
The following code will create a new object the first time, and also force other threads to wait for the creation. When the object is created, we return the cached value, and when the value is about to expire, we renew it without blocking other threads, the reference is replaced by the new value.

I'm not sure about the SemaphoreSlim &amp; ManualResetEvet parts, may be somebody can simplify this code.

Thanks.

public class UpdatableLazy&lt;T&gt;
{
    public class Container&lt;T&gt;
    {
        public readonly T Value;

        internal Container(T value)
        {
            Value = value;
        }
    }

    private readonly Func&lt;Task&lt;T&gt;&gt; _updateFunc;
    private readonly Func&lt;T, bool&gt; _isRenewNeeded;
    private volatile Container&lt;T&gt; _container;
    private volatile bool _isUpdating;
    private readonly SemaphoreSlim _ss = new SemaphoreSlim(1, 1);

    public UpdatableLazy(Func&lt;Task&lt;T&gt;&gt; updateFunc, Func&lt;T, bool&gt; isRenewNeeded)
    {
        _updateFunc = updateFunc;
        _isRenewNeeded = isRenewNeeded;
    }

    private readonly ManualResetEventSlim _mre = new ManualResetEventSlim();

    public async Task&lt;T&gt; GetValueAsync()
    {
        //The value doesn't exist yet
        if (_container == null)
        {
            //if a thread is already creating the value, we will wait for the value
            if (!await _ss.WaitAsync(0).ConfigureAwait(false))
            {
                _mre.Wait();
            }

            //After the release of the ManualResetEvent, the value should be available
            if (_container != null)
            {
                return _container.Value;
            }

            try
            {
                //Let's create the value
                _container = new Container&lt;T&gt;(await _updateFunc().ConfigureAwait(false));
                return _container.Value;
            }
            finally
            {
                //We tell awaiting threads that the value is available
                _mre.Set();
                _ss.Release();
            }
        }

        //If the value is not updating and we need to update, we replace the old value by a new one
        if (!_isUpdating &amp;&amp; _isRenewNeeded(_container.Value))
        {
            _isUpdating = true;

            try
            {
                _container = new Container&lt;T&gt;(await _updateFunc().ConfigureAwait(false));
            }
            finally
            {
                _isUpdating = false;
            }
        }

        return _container.Value;
    }
}


How I tested it :

class SomeObject
{
    public string Value = "Hello";
    public DateTime Expire = DateTime.UtcNow.AddSeconds(5);
}

class Program
{
    private static int _count;
    static void Main(string[] args)
    {
        var iteration = 10000;
        var cachedObj = new UpdatableLazy&lt;SomeObject&gt;(Create, o =&gt; DateTime.UtcNow &gt; o.Expire);
        Parallel.For(0, iteration, new ParallelOptions {MaxDegreeOfParallelism = 2048}, async i =&gt;
        {
            await cachedObj.GetValueAsync().ConfigureAwait(false);
            Interlocked.Increment(ref _count);
        });

        if (iteration != _count)
            throw new Exception("_count and iteration should be equal");

        Console.WriteLine("Done");
        Console.ReadKey();
    }

    static async Task&lt;SomeObject&gt; Create()
    {
        await Task.Delay(1000).ConfigureAwait(false);
        return new SomeObject();
    }
}
The initial creation looks thread-safe at first glance. I don't know why you await your semaphore instead of just waiting synchronously with _ss.Wait(0) but I don't think it matters much.

The updating part however is not thread-safe. Any number of threads can come in between:


if (!_isUpdating &amp;&amp; _isRenewNeeded(_container.Value))



and


_isUpdating = true;



This will result in every thread getting their own value, which is probably not what you want.

You should also call Dispose on disposable objects when you no longer need them.