我正在寻找一种优雅的方法来缓存异步操作的结果。
我最初有一个类似于这样的同步方法:
public String GetStuff(String url)
{
WebRequest request = WebRequest.Create(url);
using (var response = request.GetResponse())
using (var sr = new StreamReader(response.GetResponseStream()))
return sr.ReadToEnd();
}然后我把它变成异步的:
public async Task<String> GetStuffAsync(String url)
{
WebRequest request = WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
using (var sr = new StreamReader(response.GetResponseStream()))
return await sr.ReadToEndAsync();
}然后,我决定缓存结果,因此我不需要经常在外部查询:
ConcurrentDictionary<String, String> _cache = new ConcurrentDictionary<String, String>();
public async Task<String> GetStuffAsync(String url)
{
return _cache.GetOrAdd(url, await GetStuffInternalAsync(url));
}
private async Task<String> GetStuffInternalAsync(String url)
{
WebRequest request = WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
using (var sr = new StreamReader(response.GetResponseStream()))
return await sr.ReadToEndAsync();
}然后,我读了一篇关于缓存Task<T>如何更好的文章(o观看了一段视频),因为创建它们是很昂贵的:
ConcurrentDictionary<String, Task<String>> _cache = new ConcurrentDictionary<String, Task<String>>();
public Task<String> GetStuffAsync(String url)
{
return _cache.GetOrAdd(url, GetStuffInternalAsync(url));
}
private async Task<String> GetStuffInternalAsync(String url)
{
WebRequest request = WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
using (var sr = new StreamReader(response.GetResponseStream()))
return await sr.ReadToEndAsync();
}现在的问题是,如果请求失败(例如:HTTP401),缓存将包含一个失败的Task<String>,而我将不得不重置应用程序,因为重新发送请求是不可能的。
是否有一种优雅的方法可以使用ConcurrentDictionary<T1,T2>缓存成功的任务,并且仍然具有原子行为?
发布于 2014-02-07 12:26:49
首先,您的两种方法都是错误的,因为它们不会为您节省任何请求(尽管第二种方法至少可以节省您的时间)。
您的第一个代码(带有await的代码)执行以下操作:
第二段代码删除了步骤2,因此速度更快,但仍然会发出大量不必要的请求。
相反,您应该使用这需要一名代表
public Task<String> GetStuffAsync(String url)
{
return _cache.GetOrAdd(url, GetStuffInternalAsync);
}这并不能完全消除被忽略的请求的可能性,但它确实大大降低了这些请求的可能性。(为此,您可以尝试取消您知道正在被忽略的请求,但我认为这不值得我们为此付出努力。)
现在谈谈你的实际问题。我认为你应该做的是使用方法。如果值还没有存在,那么添加它。如果它在那里,如果它有故障,就更换它:
public Task<String> GetStuffAsync(String url)
{
return _cache.AddOrUpdate(
url, GetStuffInternalAsync, (u, task) =>
{
if (task.IsCanceled || task.IsFaulted)
return GetStuffInternalAsync(u);
return task;
});
}发布于 2014-02-07 12:44:21
将这些失败的任务保留为负缓存实际上是合理的(并且取决于您的设计和性能,这是至关重要的)。否则,如果一个url总是失败,那么一次又一次地使用它就完全失败了。
您需要的是一种不时清除缓存的方法。最简单的方法是拥有一个替换ConcurrentDictionarry实例的计时器。更健壮的解决方案是构建自己的LruDictionary或类似的东西。
发布于 2016-01-17 03:33:15
另一种简单的方法是将Lazy<T>扩展为AsyncLazy<T>,如下所示:
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<Task<T>> taskFactory, LazyThreadSafetyMode mode) :
base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap(), mode)
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}然后你就可以这样做:
private readonly ConcurrentDictionary<string, AsyncLazy<string>> _cache
= new ConcurrentDictionary<string, AsyncLazy<string>>();
public async Task<string> GetStuffAsync(string url)
{
return await _cache.GetOrAdd(url,
new AsyncLazy<string>(
() => GetStuffInternalAsync(url),
LazyThreadSafetyMode.ExecutionAndPublication));
}https://stackoverflow.com/questions/21626014
复制相似问题