返回内存(缓存)集合或异步等待响应中的任务

时间:2017-05-23 13:47:03

标签: c# asp.net async-await

我有一个场景,我的服务类与API通信并填充列表。

这个方法
 public async Task<List<T>> Foo()

在这个方法中,我正在进行异步等待,以便从API中检索数据并反序列化为列表。

问题是,我想做这样的事情:

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

但是if语句的第一部分中的返回类型是List<T>,而第二部分的返回类型是Task<List<T>>

我该怎么做?我可以将我的内存列表作为任务吗? (也许我可以将List包装在一个任务中)?

谢谢大家!

4 个答案:

答案 0 :(得分:5)

我不会在此处讨论技术详细信息,但请考虑将await展开为Task<T>并将T方法展开为包装其返回值{{ 1}}进入async

所以,如果你的方法签名就像你问的那样

T

然后您的返回值与您的评论不同:

Task<T>

所以,无论如何,public async Task<List<T>> Foo() 的{​​{1}}语句需要GetDataFromApi(); // results in Task<List<T>> await GetDataFromApi(); // results in List<T> 而不是return

async Task<List<T>>

如果您不使用async await,则可以将结果包装在已完成的任务中:

List<T>

请注意,在第二个示例中删除了Task<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 }

答案 1 :(得分:3)

我经常发现Lazy<T>在处理第一次调用时只需要计算值的函数时很有用。

我会考虑使用他的AsyncEx库中的Stephen Cleary的AsyncLazy<T>,它是Lazy<T>的异步版本,非常适合缓存值属性,您可以使用属性替换您的方法:< / p>

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

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

并使用这样的属性:

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

有关Stephen Cleary的Async OOP 3: Properties博客文章中的异步缓存值的更多信息。

答案 2 :(得分:2)

这称为memoization。函数会记住以前的值,因此不必重新计算它们。这是函数式语言中常用的技术。

您可以创建一个通用memoize函数,该函数采用一种方法并处理缓存。这很容易在C#7中编写。在C#6中,您必须定义一个Func变量,这是一个小小的丑陋:

    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;
    }

一旦有了Memoize,就可以将任何函数(包括任何异步函数)转换为可缓存的函数,例如:

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);

您可以通过创建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;
    }

这将创建缓存的函数,而不在类型列表中指定任务:

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

<强>更新

如果您在不测试代码的情况下更改帖子,那就是您所获得的。感谢Servy注意到

存储任务而不是结果可以使代码更简单,而无需更改调用者:

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;
}

在C#4中,相同的代码是:

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);         
}

记忆并调用此测试函数两次,只打印一次消息:

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"));

这将打印:

Working
a a

答案 3 :(得分:0)

您应该将List包装在Task中。使用此代码。

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