Storing cache value that is returned from awaitable method
g
I am having a bit of a problem saving a value to cache that is produced by an awaitable method. I'm using Umbraco Isolated cache in order to save some API endpoint responses. I can't find a smart way to pass a getter method. 1. Problem:
Copy code
var cachedValue = _cache.GetOrAdd<Weather>(key, async () =>
{
    var response = await _dataClient.MakeRequest<Weather>(requestDescriptor);
    return response.Data;
});
Error:
Cannot convert async lambda expression to delegate type 'Func<Weather>'. An async lambda expression may return void, Task or Task<T>, none of which are convertible to 'Func<Weather>'.
I can solve this by replacing the type and awaitng cache, but I'd not like to cache `Task`:
Copy code
var cachedValue = await _cache.GetOrAdd<Task<Weather>>(key, async () =>
{
    var response = await _dataClient.MakeRequest<Weather>(requestDescriptor);
    return response.Data;
});
Then I tried to split the logic of getting and setting the cached values, but the problem is, that also the
Insert
or
InsertCacheItem
required getter method as paramter, which leads again to storing a
Task
.
Copy code
var cachedValue = (T)_cache.Get(cacheKey);
if (cachedValue != null)
    return cachedValue;
else
{
    var getterResult = await getter();
    _cache.Insert(cacheKey, <I'd like to put here getter result instead of getter func>);
}
What is the suggested way of doing this?
j
Hey Gregor 👋 The fundamental problem you've got is that you are trying to cache a
Weather
object, but because that
Weather
object is the result of an async task it may not actually exist at the point that you are trying to put it in the cache. You've got a few options. Firstly, why not cache the task? It's only a lightweight state container, AFAIK there's no real downside to caching a task beyond a little extra memory. It's also good practice to keep the return types the same - both the cached and non-cached versions of your client/service will return the same type
Task<Weather>
. You could run the task synchronously (
_dataClient.MakeRequest<Weather>(requestDescriptor).Reult
) so that the result of the task can be cached. Obviously this has the downside of losing the performance benefit of async by blocking the thread. Finally, you could consider eagerly caching
Weather
- by calling the API on a schedule and populating the cache ahead of time.
g
The thing with cacheing task is, that I'd like to check if the request finished successfully and not cache failed one.
j
I thought that throwing inside the value factory would stop the Task from getting persisted to the cache... it does not (TIL). So yeah, best not to cache tasks that you don't have high confidence in. In which case, splitting the get/set as you tried is probably the way to go. You can just wrap the result in a lambda/func to make that work.
Copy code
csharp
if (_cache.Get(cacheKey) is T cachedValue)
    return cachedValue;
else
{
    var getterResult = await getter<T>();
    _cache.Insert(cacheKey, () => getterResult);
    return getterResult;
}
g
Thank you.
j
You're welcome.
4 Views