2017-02-18 45 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); 
     } 
    } 

Но я не понимаю, как эта защита может быть эффективной. Я предполагаю, что обработчик события клика будет что-то вроде этого:

_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... 
     }; 

Если по какой-то причине загрузка запускается первым кликом еще в процессе, а затем кнопка нажата в течение 2-я раза; не собирается ли он еще дважды загрузить страницу, так как кэш еще не был заполнен с первой загрузки к моменту запуска 2-го клика? Если мое понимание ошибочно, объясните, почему. В противном случае, как реализовать такой кеш-защиту, повторный щелчок пользователя?

Кстати, я понимаю, что кеш работает эффективно, если он используется вне контекста обработчика событий (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' нет' ожидания ':) –

+0

@LucasTrzesniewski это намеренно не ждет, чтобы спровоцировать параллелизм! – stt106

+0

Точно, код не добавит второй задачи, когда уже выполняется задание (или если оно уже завершено в этом отношении) :) –

ответ

1

Словарь _futureCache является кэширование Task на основе uri ключа. Это мешает вам создать новый Task при запросе веб-страницы и вместо этого вернет Task, который был уже создан в последний раз, когда uri был запрошен - осталось или нет Task. Затем вызывающий код ожидает того же Task для каждого запроса к этому uri. Если он уже завершен, он немедленно возвращается с результатом Task. Если нет, он ждет, пока он не будет завершен. Независимо от этого, он будет извлекать страницу только один раз.

+0

Возможно, я не понимаю вас, но когда 1-й вызов ури и нет кеша, он запросит загрузку, пока эта загрузка выполняется, вы говорите, что кэш уже заполнен текущей задачей? – stt106

+0

'GetWebPage' не помечен' async'. Он возвращает задание, но не дожидается завершения. Таким образом, он идет в кеш, в то время как блокировка монитора на '_futureCache' удерживается для предотвращения условий гонки. Второй запрос будет блокироваться там в течение нескольких наносекунд, пока Задача не будет * запущена * и добавлена ​​в кэш. – Tim

+0

Хорошо, что это означает то же самое, что и мой первый комментарий, например, как только начинается первая задача, хотя она все еще продолжается, она уже кэшируется. Таким образом, второй запрос может получить неполную задачу из кеша, но все равно не вызовет другой запрос/задачу? – stt106

 Смежные вопросы

  • Нет связанных вопросов^_^