Lazy async initialization for expiring objects

Today I wanted to share something I found myself using quite a lot recently, and that is not supported out of the box by the .NET framework.

So, as part of the framework, we have Lazy<T>, which provides out of the box support for deferring the creation of a large or resource-intensive objects.

However, what if the object requires async operation to be created, and what if its value expires after some time, and it needs to be recomputed? Let’s have a look at how to solve this.

Sample use case

Let’s imagine the following scenario – you are making service calls to an HTTP API using a typical OAuth 2.0 client credentials flow. This means, you need to obtain the token from the identity server in order to be able to use it to call the resource server. Typically you’d only fetch the token once it’s needed, and the operation of retrieving the token should be async, as it’s network bound. Additionally, according to the OAuth spec, client credentials flow doesn’t support refresh tokens, so once the original access token expires, you’ll need to re-request a new one.

All of that means that we are dealing with a lazy initialization (get the token only once we need it for the first time), with an async operation (network bound) and with an object that will expire (access token has a limited lifetime).

Let’s call our construct AsyncExpiringLazy<T> and support this scenario in a generic way.

Building an AsyncExpiringLazy<T>

In addition to the construct itself, we will also need a simple wrapper around the result object and its expiration timestamp, so let’s start there.

So far so good – not much to explain there. Next let’s add AsyncExpiringLazy. Since properties can’t be async in C#, in our AsyncExpiringLazy, we’ll need to use an instance method as a way to fetch our underlying value.

Let’s first create the outline of the class, and then fill in the remaining blanks.

So the constructor will take in a value provider – the provider itself will receive the “previous/old”, expired (or expiring) value and will be responsible of providing a new one – wrapped in the ExpirationMetadata defining it’s expiration time.

Filling in the remaining blanks is surprisingly easy – it’s shown below. The only “trick” in the code is to use SemaphoreSlim for locking – since C# does not allow traditional lock statements to contain awaits.

Regarding invalidation, we can just reset the ExpirationMetadata back to its default value.

The code is shown below.

And that’s it.


Usage is quite simple and is shown in the next unit test:

You can create an instance of AsyncExpiringLazy at any point – for example it could be a field in some class of yours. You will need to pass in a delegate responsible for value creation – it will give you a chance to inspect the old value too if needed.

From there on, it’s all about accessing the value whenever you need it. And if it expires, AsyncExpiringLazy will re-create it on next access.

All the code for this article is located here on Github as .NET Standard 1.3 library. Hope this helps in some way.

  • MuiBienCarlota

    Just a question. Why not a ConfigureAwait(false) on the second await _syncLock.WaitAsync(); in async Task Value()?

    • Filip W

      oops this was my mistake – thanks!

  • Steve Hansen

    Awesome simple helper class, just a two questions.

    Why do you release and then get a new lock inside the Value() method, in theory we can still have 2 requests calling Value() at the same time, which will call the valueFactory twice where the second lock will overwrite the _value field.
    Inside the first try/finally I would go with code like the following:

    if (!IsValueCreatedInternal)
    _value = await _valueProvider(_value).ConfigureAwait(false);

    return _value.Result;

    My second question is about the use-case why you pass the _value to the valueProvider, it makes the usage a bit weird (new AsyncExpiringLazy(async metadata =>) because I don’t know why you would ever need the metadata variable there, async () => looks more logical to me, it’s in-line with how Async works.

  • Denis Ibragimov

    Very good code!
    I would probably add
    public Task Value => ValueAsync();
    as closer to original Lazy, and then
    public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); }
    to make more task-styled calls
    var token = await testInstance;

  • Francesco

    Very useful, thanks for sharing!

  • Andy Pook

    A bit late to this… Like it, but…

    Q: why take the lock twice. Doesn’t this expose you to some race conditions?
    Why not just remove lines 38-46?

    Also, what Denis said :)