2017-02-18 64 views
0

我读的“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); 
     } 
    } 
+1

该代码正常工作。提示:'GetWebPage'中没有'await' :) –

+0

@LucasTrzesniewski它故意没有等待煽动并发! – stt106

+0

没错,当已经有进行中的任务(或者,如果它已经完成了对这个问题)的代码不会添加第二个任务:) –

回答

1

_futureCache字典是基于uri关键缓存一个Task。要求该网页时,它阻止您创建一个新的Task,而是将返回一个已经创建的最后一次uri是request--是Task是否已完成Task。然后调用代码等待相同的Task对该uri的每个请求。如果已经完成,则立即返回该结果Task。如果没有,它会一直等到它完成。无论如何,它只会获取一次页面。

+0

也许我不理解你,但当第一次调用uri并且没有缓存时,它会请求下载,而此下载正在进行中,您是否说缓存已经填充到正在进行的任务中? – stt106

+0

'GetWebPage'没有标记'async'。它会返回一个任务,但不会等待它完成。所以它保存在缓存中,同时监视'_futureCache'上的监视器锁定以防止竞争条件。第二个请求会在那里阻塞几个纳秒,直到Task开始*并添加到缓存。 – Tim

+0

好的,我认为这意味着和我的第一个评论一样,比如一旦第一个任务开始,即使它仍然在进行,它已经被缓存了。所以第二个请求可能会从缓存中得到一个不完整的任务,但仍然不会触发另一个请求/任务? – stt106