Return either an in-memory (cached) collection or a task from an async response awaiting

I have a scenario where my service class binds to an API and populates a list. This method

 public async Task<List<T>> Foo()

      

and in this method i am doing async, waiting for data to be received from API and deserialize to list.

The thing is, I would like to do something like this:

if (list is cached in memory)
   //serve data from the in memory list
else
   //await get data from the API

      

But the return type in the first part of the if statement is equal List<T>

, whereas the return type of the second part isTask<List<T>>

How can i do this? Can I just have a list in memory as a task? (Maybe I can wrap the list in a task)?

Thanks everyone!

+3


source to share


4 answers


You have to wrap the list inside a task. Using this code.



if (list is cached in memory)
  return Task.FromResult(list);
else
  return await GetResultFromApi();//returns Task<List<T>>

      

+1


source


I won't go into technical details here, but think about await

how to wrap Task<T>

in T

and async

how to wrap its return value T

in Task<T>

.

So if your method signature is like your request

public async Task<List<T>> Foo()

      

Then your return value is different from your comment:

GetDataFromApi(); // results in Task<List<T>>
await GetDataFromApi(); // results in List<T>

      



Anyway, the operator return

async Task<List<T>>

expects List<T>

, notTask<List<T>>

public async Task<List<T>> Foo()
{
    if (list is cached in memory)
        return list; //serve data from the in memory list
    else
        return await GetDataFromApi(); //await get data from the API
}

      

If you are not using async await, you can wrap the result in a completed task:

public Task<List<T>> Foo()
{
    if (list is cached in memory)
        return Task.FromResult(list); //serve data from the in memory list
    else
        return GetDataFromApi(); //await get data from the API
}

      

Note that both, async

and await

are removed in this second example.

+4


source


I often find it Lazy<T>

helpful when working with functions that only need to compute values ​​the first time they are called.

I would consider using Stephen Cleary AsyncLazy<T>

from his AsyncEx library, which is an async version Lazy<T>

and is great for the cached value property, you can replace your method with a property:

public class SomeClass<T>
{
    public SomeClass()
    {
        Foo = new AsyncLazy<List<T>>(async ()=> 
        {
            var data = await GetDataAsync();
            return data;
        });
    }

    public AsyncLazy<List<T>> Foo { get; } 
}

      

And use a property like this:

var someClass = /*get SomeClass somehow*/
var foo = await someClass.Foo;

      

More on async cached values ​​in Stephen Cleary Async OOP 3: Properties on the blog.

+3


source


This is called memoization. The function remembers the previous values, so it doesn't need to be recalculated. This is a common technique used in functional languages.

You can create a generic function memoize

that uses one method and handles caching. It's easy to write this in C # 7. In C # 6, you need to define the Func variable, which is the littel bit uglier:

    public Func<TIn, TOut> Memoize<TIn, TOut>(Func<TIn, TOut> f)
    {
        var cache = new Dictionary<TIn, TOut>();
        TOut Run (TIn x)
        {
            if (cache.ContainsKey(x))
            {
                return cache[x];
            }
            var result = f(x);
            cache[x] = result;
            return result;
        }
        return Run;
    }

      

Once you have Memoize, you can convert any function, including any asynchronous function, to a cache function, for example:

async Task<List<Order>> foo(int customerId)
{
    ..
    var items= await ...;
    return items
}

var cachedFunc=Memoize<int,Task<List<Order>>>(foo);
...
var orders=await cachedFunc(someId);
var sameOrders=await cachedFunc(someId);
Debug.Assert(orders=newOrders);

      

You can simplify your code a bit by creating a version MemoizeAsync

:

    public Func<TIn, Task<TOut>> MemoizeAsync<TIn, TOut>(Func<TIn, Task<TOut>> f)
    {
        var cache = new Dictionary<TIn, TOut>();
        async TOut Run (TIn x)
        {
            if (cache.ContainsKey(x))
            {
                return cache[x];
            }
            var result = await f(x);
            cache[x] = result;
            return result;
        }
        return Run;
    }

      

This will create a cached function without specifying the task in the type list:

var cachedFunc=MemoizeAsync<int,List<Order>>(foo);

      

UPDATE

What do you get if you change the post without testing the code. Thanks to Servy for taking notes

Storing the task instead of the result can make the code even easier without changing the callers:

public Func<TIn, System.Threading.Tasks.Task<TOut>> MemoizeAsync<TIn, TOut>(Func<TIn, Task<TOut>> f)
{
    var cache = new ConcurrentDictionary<TIn, System.Threading.Tasks.Task<TOut>>();
    Task<TOut> Run (TIn x) => cache.GetOrAdd(x, f);         
    return Run;
}

      

In C # 4, this same code would look like this:

public Func<TIn, Task<TOut>> MemoizeAsync<TIn, TOut>(Func<TIn, Task<TOut>> f)
{
    var cache = new ConcurrentDictionary<TIn, Task<TOut>>();
    return x => cache.GetOrAdd(x, f);         
}

      

Memorizing and calling this test function twice, prints out the message only once:

Task<string> test(string x)
{
    Console.WriteLine("Working");
    return Task.FromResult(x);
}

var cachedFunc=MemoizeAsync<string,string>(test);
var results=await Task.WhenAll(cachedFunc("a"),cachedFunc("a"));

      

This will print:

Working
a a

      

+1


source







All Articles