我正在阅读“Cuts in a Nutshell”中的异步函数部分。一个例子如下:
假设我们需要处理“下载”按钮点击事件,我们想要缓存下载结果。如果我们不关心下载相同的网站,以防重复点击该按钮,那么它就是eaiser。
private static Dictionary<string, string> _cache = new Dictionary<string, string>();
async Task<string> DownloadAsync(string uri)
{
string content;
if (_cache.TryGetValue(uri, out content)) return content;
return _cache[uri] = await new WebClient().DownloadStringTaskAsync(uri);
}
但是这意味着它会向同一个网站发出多余的下载,例如,如果连续两次点击它!为了保护这一点,本书建议缓存Task<string>
。
private static Dictionary<string, Task<string>> _futureCache = new Dictionary<string, Task<string>>();
Task<string> GetWebPage(string uri)
{
lock (_futureCache)
{
Task<string> task;
if (_futureCache.TryGetValue(uri, out task)) return task;
return _futureCache[uri] = new WebClient().DownloadStringTaskAsync(uri);
}
}
但我不明白这种保护是如何有效的。我假设click事件处理程序是这样的:
_downloadBtn.Click += async (sender, args) =>
{
var uri = "http://www.bbc.co.uk"; // for argument sake, it always downloads bbc...
string result = await GetWebPage(uri);
// process the result...
};
如果由于某种原因,第一次点击触发的下载仍在进行中,然后第二次点击该按钮;是不是仍会继续下载页面两次,因为在第二次点击被触发时尚未从第一次下载填充缓存?如果我的理解是错误的,请解释原因。否则,如何实现这样的缓存保护重复用户点击?
顺便说一下,我知道如果缓存在事件处理程序(UI)上下文之外使用,缓存有效地工作,例如下面只下载一次。
async void Foo()
{
var uri = "http://www.bbc.co.uk";
string result;
for (int i = 0; i < 2; i++) //Stimulate repeated call
{
result = await GetWebPage(uri);
Console.WriteLine(result.Length);
}
}
答案 0 :(得分:1)
_futureCache
字典基于Task
密钥缓存uri
。它会阻止您在请求网页时创建新的Task
,而是会返回上次Task
请求时已创建的uri
- 无论是否Task
已完成。然后,对于Task
的每个请求,调用代码等待相同的uri
。如果已经完成,则立即返回Task
的结果。如果没有,它会一直等到它完成。无论如何,它只会获取一次页面。