Eager refresh of values for AsyncExpiringLazy

Some time ago I blogged about introducing a new library, called AsyncExpiringLazy, which can be used for managing lazy-resolved values that expire and must be refreshed – such as for example access tokens to web APIs.

Yesterday I pushed out a release 2.1.0 of the library, which features a unique new feature – built thanks to the great work of Lukasz – some new additional semantics for the way how the captured value gets refreshed.

Recap on AsyncExpiringLazy

The main feature of the library is represented by the AsyncExpiringLazy<T> class. The way it works, is that it will resolve the value using the provided factory logic upon first use (hence the name “lazy”). Then, the value will expire according to the predefined expiration metadata, and upon first use after it expired, it will be refreshed – resolved via the factory again.

The downside of this is that when the factory logic is slow (e.g. due to slow I/O or external dependencies), the first use after expiration would be slower too, potentially affecting the user experience. The upside is that the renewal would only happen if/when it is really needed – as long as the expired value is no longer attempted to be accessed, no “wasteful” refresh happens. We can illustrate this with a simple example.

Consider the following code. AsyncExpiringLazy wraps a factory method that produces a random integer in the range of 0-1000, which is marked to be valid for four seconds. To simulate a slowish behavior of the factory, we are using an artificial 400 millisecond delay.

As long as the value is considered valid, that is in the four second time window, the same value is reused and returned to the caller. As soon as it expires, the next access to the lazy value will re-trigger the factory method. To help visualize what is going on, we can spit out a simple console message indicating that the value was refreshed.

We can run the code in an infinite loop, that accesses the value at regular intervals, for example every second, to see the effects.

The output should be similar to:

Because the value expires every four seconds, and we access it every second, every fourth access is slower – because of the lazy refresh. Since we chose an arbitrary overhead of 400ms, this is tacked upon the actual value access. Depending on your use cases, this may or may not be what you want.

Introducing AsyncExpiringEager

Enter a new type in the library, which we called AsyncExpiringEager<T>. In the eager version, the semantics of the refresh of the value are changed – it is no longer refreshed on first access after expiration, but silently as soon as the old one expires, using a background task. AsyncExpiringEager is disposable, and disposing of it also shuts down this background monitor task.

Just like before, we can illustrate this with an example. To better compare AsyncExpiringLazy with AsyncExpiringEager, we shall use the same RandomIntegerFactory as before.

When invoked in a similar infinite loop, with one second intervals:

The output should resemble:

The initial population of the value shows the extra 400ms overhead – that is because we still deal with a “lazy” construct, so it gets initialized on first usage. However, the subsequent accesses are always fast, because the refresh of the value happens behind the scenes and the first access after expiration no longer suffers from the factory-induced delay.

Of course, this works this exact way because of how access patterns and refresh time align against each other. Should we shuffle them up, it might obviously happen that some accesses of the value would happen exactly at the moment as the value is being refreshed and some slow down may be observed then. But in principle, this model provides an alternative to the default use case, giving more flexibility to you as the person writing code. In particular, AsyncExpiringEager works well for scenarios where the value is expensive to compute.

You get it from Nuget as version 2.1.0 now.