缓存异步函数结果在C#
我读的“C#果壳中的”异步功能部分。一个例子是如下:缓存异步函数结果在C#
假设我们需要处理“下载”按钮,单击事件,我们希望缓存下载结果。如果我们不在乎下载相同的网站万一按钮被重复点击,那么它是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);
}
}
的_futureCache
字典是基于uri
关键缓存一个Task
。要求该网页时,它阻止您创建一个新的Task
,而是将返回一个已经创建的最后一次uri
是request--是Task
是否已完成Task
。然后调用代码等待相同的Task
对该uri
的每个请求。如果已经完成,则立即返回该结果Task
。如果没有,它会一直等到它完成。无论如何,它只会获取一次页面。
也许我不理解你,但当第一次调用uri并且没有缓存时,它会请求下载,而此下载正在进行中,您是否说缓存已经填充到正在进行的任务中? – stt106
'GetWebPage'没有标记'async'。它会返回一个任务,但不会等待它完成。所以它保存在缓存中,同时监视'_futureCache'上的监视器锁定以防止竞争条件。第二个请求会在那里阻塞几个纳秒,直到Task开始*并添加到缓存。 – Tim
好的,我认为这意味着和我的第一个评论一样,比如一旦第一个任务开始,即使它仍然在进行,它已经被缓存了。所以第二个请求可能会从缓存中得到一个不完整的任务,但仍然不会触发另一个请求/任务? – stt106
该代码正常工作。提示:'GetWebPage'中没有'await' :) –
@LucasTrzesniewski它故意没有等待煽动并发! – stt106
没错,当已经有进行中的任务(或者,如果它已经完成了对这个问题)的代码不会添加第二个任务:) –