2012-03-28 4 views
9

У меня есть несколько случаев, когда я использую ConcurrentDictionary<TKey, TValue> для кеширования значений, но часто мне нужно выполнить проверку значения, чтобы решить, добавлять ли его в кеш, используя ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey, Func<TKey, TValue>).Как я могу сказать `ConcurrentDictionary.GetOrAdd`, чтобы не добавлять значение?

Обычно вдоль линий:

private readonly ConcurrentDictionary<Type, ISomeObject> someObjectCache = 
    new ConcurrentDictionary<Type, ISomeObject>(); 
public ISomeObject CreateSomeObject(Type someType) 
{ 
    return someObjectCache.GetOrAdd(someType, type => 
    { 
     if(!Attribute.IsDefined(someType, typeof(SomeAttribute)) 
      // Do something here to avoid the instance from being added to 
      // `someObjectCache` 

     ISomeObject someObject; 
     // Typical factory functionality goes here 
     return someObject; 
    }); 
} 

так, как я справиться с этим сегодня бросить исключение, которое, как представляется, отлично работает, но я хотел бы более чистый подход (возможно, флаг можно установить или конкретное значение, которое я могу установить для возвращаемого значения), чтобы отменить GetOrAdd в пределах лямбда (хотя это можно было бы реально заменить методом полного выдувания).

Основываясь на моем опыте с другими LINQ, как методы, возвращение null приведет к стоимости получения добавляется без проверки как таковой (и чтение IL для GetOrAdd он выглядит, как он будет приводить к одной и той же проблемы), так что я не думайте, что это сработает.

Есть ли способ избежать использования исключений для отмены добавления с помощью GetOrAdd?

ответ

10

Из того, что я прочитал, есть no guarantee that the Add factory method will only be called a single time amongst all callers to Get for the same key.

Соответствующая часть с этой страницы находится в нижней части, цитируемые здесь:

Кроме того, хотя все методы ConcurrentDictionary (Of TKey, TValue) потокобезопасны, не все методы являются атомарными, в частности, GetOrAdd и AddOrUpdate. Делегат пользователя, который передается этим методам, - это , вызываемый вне внутренней блокировки словаря. (Это делается для предотвратить неизвестный код блокировать все темы.) Поэтому возможно эта последовательность событий происходит:

1) ThreadA называет GetOrAdd, не находит пункт и создает новый элемент для добавления по вызывая делегат valueFactory.

2) threadB называет GetOrAdd одновременно, его valueFactory делегат испробованы и он поступает на внутренний замок перед ThreadA, и поэтому его новая пара ключ-значение добавляется в словарь.

3) пользователь делегат ThreadA завершает, и поток поступает в замок, но теперь видит, что элемент уже существует

4) ThreadA выполняет «Get», и возвращает данные, которые были ранее добавляемые by threadB.

Таким образом, он не гарантируется, что данные, возвращаемые GetOrAdd - это те данные, которые была создана пользователем valueFactory. Подобная последовательность событий может возникать при вызове AddOrUpdate .

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

Таким образом, вам не нужно, чтобы добавить дальнейшую блокировку, и вместо того, чтобы можно было бы использовать следующий шаблон:

private ConcurrentDictionary<Type, ISomeObject> someObjectCache = 
    new ConcurrentDictionary<Type, ISomeObject>(); 
public ISomeObject CreateSomeObject(Type someType) 
{ 

    ISomeObject someObject; 
    if (someObjectCache.TryGet(someType, out someObject)) 
    { 
     return someObject; 
    } 

    if (Attribute.IsDefined(someType, typeof(SomeAttribute)) 
    { 
     // init someObject here 
     someObject = new SomeObject(); 

     return someObjectCache.GetOrAdd(someType, someObject); // If another thread got through here first, we'll return their object here. 
    } 

    // fallback functionality goes here if it doesn't have your attribute. 
} 

Да, это приведет к некоторым потенциалом для новых объектов, которые будут созданы потенциально несколько раз , но вызывающие все получат одинаковый результат, даже если вызывается несколько. То же, что и GetOrAdd.

+0

Это было бы (и _I have_), но как только вы загромождаете это всеми необходимыми механизмами блокировки нити, оно становится довольно грязным (не то, что моя не требует блокировки ... просто не в той же степени). Я ищу более чистое решение, а не тот, который требует, чтобы вы эффективно сворачивали мою собственную версию 'ConcurrentDictionary'. –

+0

Обновлен мой ответ - GetOrAdd не блокирует часть добавления, только при работе с битами внутреннего массива. Поэтому, как я вижу, вам не нужны никакие блокировки, если вы намерены добавить подтверждение. –

+0

Да, я прочитал эту часть документов, но это все еще не то, что я ищу. Если бы я захотел написать столько кода, я бы сам справился с блокировкой. –

3

Все проблемы в информатике может быть решена другой уровень косвенности

// the dictionary now stores functions 
private readonly ConcurrentDictionary<Type, Func<ISomeObject>> someObjectCache = 
    new ConcurrentDictionary<Type, Func<ISomeObject>>(); 

public ISomeObject CreateSomeObject(Type someType) { 
    return someObjectCache.GetOrAdd(someType, _ => { 
    if(ShouldCache(someType)) { 
     // caching should be used 
     // return a function that returns a cached instance 
     var someObject = Create(someType); 
     return() => someObject; 
    } 
    else { 
     // no caching should be used 
     // return a function that always creates a new instance 
     return() => Create(someType); 
    } 
    })(); // call the returned function 
} 

private bool ShouldCache(Type someType) { 
    return Attribute.IsDefined(someType, typeof(SomeAttribute)); 
} 

private ISomeObject Create(Type someType) { 
    // typical factory functionality ... 
} 

Теперь значение, хранящееся в словаре является функция; когда вы не хотите, чтобы кеширование происходило, функция всегда создает новый экземпляр; когда вы хотите, чтобы кеширование произошло, функция возвращает экземпляр кэширования.

+0

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

+0

Несомненно, я добавлю еще ... –

+0

Перемещение чека в отдельную функцию не имеет ничего общего с сердцем вопроса, это был просто общий рефакторинг .... –