我有一个场景,我的服务类与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包装在一个任务中)?
谢谢大家!
答案 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>>