-1

I am using the following pattern with MemoryCache:

public static T GetFromCache<T>(string key, Func<T> valueFactory) {
    var newValue = new Lazy<T>(valueFactory);
    var oldValue = (Lazy<T>)cache.AddOrGetExisting(key, newValue, new CacheItemPolicy());
    return (oldValue ?? newValue).Value;
}

And call it:

var v = GetFromCache<Prop>(request.Key, () => LongCalc());

This works well enough. However, when LongCalc throws an exception, cache.AddOrGetExisting saves the exception into the cache.

I am trying to identify when that happens via:

if (oldValue != null && oldValue.Value.GetType() == typeof(Exception)) {
  cache.Remove(key, CacheEntryRemovedReason.Evicted);
}

but simply calling oldValue.Value throws an exception.

How can I identify whether oldValue object contains an exception and deal with it accordingly?

AngryHacker
  • 59,598
  • 102
  • 325
  • 594
  • 1
    What would be the right reaction? If you remove the value will the same exception with the same cached item not happen again and you are stuck in an endless loop? If you retry that in a catch handler and then call GetFromCache there again you kill your application fast with a StackoverFlowException which happens only when you get back an exception. Since you have already wrapped the access to the cache in this method you can catch the exception there already but the problem stays what do you want to do? Try again? – Alois Kraus Mar 12 '19 at 23:04

1 Answers1

1

That's wrong approach. It never works with lazy evaluation, especially if you'd have to lift it to a multithreaded environment once. Pass a (ideally, pure - i.e. stateless) callback Action<Exception> as an optional parameter and make sure there is reasonable yet generic enough default implementation.

Or, at least, provide your custom ExceptionAwareLazy<T> which would hold appropriate flag. Such a solution gonna be far worse, however no async callbacks would bother you.

P.S. ReactiveExtensions are good examples of the approach I've suggested. You can easily wrap your solution into TaskCompletionSource and stick to well-known async/await.

Zazaeil
  • 3,900
  • 2
  • 14
  • 31