2015-07-16 4 views
3

Один из примеров использования Task Я нашел на MSDN (found here) кажется довольно странным. Существует ли какая-либо причина для класса Lazy<T>?Есть ли какие-либо цели для Lazy <T> в этом примере кода?

public class AsyncCache<TKey, TValue> 
{ 
    private readonly Func<TKey, Task<TValue>> _valueFactory; 
    private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map; 

    public AsyncCache(Func<TKey, Task<TValue>> valueFactory) 
    { 
     if (valueFactory == null) throw new ArgumentNullException("loader"); 
     _valueFactory = valueFactory; 
     _map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>(); 
    } 

    public Task<TValue> this[TKey key] 
    { 
     get 
     { 
      if (key == null) throw new ArgumentNullException("key"); 
      return _map.GetOrAdd(key, toAdd => 
       new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value; 
     } 
    } 
} 

Как только Lazy<Task<TValue>> создан, он немедленно доступны. Если к нему обращаются немедленно, то использование Lazy<T> только добавляет дополнительные служебные данные и делает этот пример более запутанным, чем нужно. Разве я здесь что-то не хватает?

ответ

4

Вы правы, что он будет создан, то сразу же доступны, но главное, чтобы отметить, вы не всегда использовать объект создается.

GetOrAdd функция словаря действует как Lazy<T> с LazyThreadSafetyMode.PublicationOnly что означает делегат вы передаете в качестве функции фабрики может быть выполнена более чем один раз, но только первый закончить будет возвращен всем абонентам.

По умолчанию Lazy<T> - LazyThreadSafetyMode.ExecutionAndPublication, что означает, что первый человек, которому позвонил заводская функция, получит блокировку, и любые другие абоненты должны дождаться завершения заводской функции до продолжения.

Если мы переформатируем метод get, он станет немного более понятным.

public Task<TValue> this[TKey key] 
{ 
    get 
    { 
     if (key == null) 
      throw new ArgumentNullException("key"); 
     var cacheItem = _map.GetOrAdd(key, toAdd => 
          new Lazy<Task<TValue>>(() => _valueFactory(toAdd))); 
     return cacheItem.Value; 
    } 
} 

Таким образом, если два потока и вызов this[Tkey key] одновременно и оба они достигают GetOrAdd без значения в словаре с принятым в ключе, то new Lazy<Task<TValue>>(() => _valueFactory(toAdd))будет выполняться дважды, однако только первым для завершения возвращается к обоим вызовам. Это не большое дело, потому что _valueFactory еще не выполнен, и это дорогая часть, все, что мы делаем, это сделать новый Lasy<T>.

После GetOrAdd вызова возвращает вас всегда будут работать с таким же один объектом, то есть, когда .Value называется, это использует режим ExecutionAndPublication и будет блокировать любые другие вызовы .Value до _valueFactory finshes выполнения.

Если мы не использовали Lazt<T>, тогда _valueFactory получил бы несколько раз, прежде чем был возвращен один результат.

1

ConcurrentDictionary не гарантирует, что ваша фабрика значений выполняется только один раз. Это делается для того, чтобы избежать вызова кода пользователя под внутренней блокировкой. Это может привести к взаимоблокировкам, что является плохим дизайном API.

Можно создать несколько лени, но только один из них будет фактически реализован, потому что словарь только когда-либо возвращает один из них.

Это гарантирует гарантированное одноразовое исполнение. Это стандартный шаблон.