2012-03-28 8 views
9

Mam kilka przypadków, w których używam ConcurrentDictionary<TKey, TValue> do buforowania wartości, ale często muszę wykonać weryfikację wartości, aby zdecydować, czy dodać ją do pamięci podręcznej przy użyciu ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey, Func<TKey, TValue>).Jak mogę powiedzieć, że `ConcurrentDictionary.GetOrAdd` nie dodaje wartości?

Zazwyczaj wzdłuż linii:

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; 
    }); 
} 

The Way I obsłużyć to dzisiaj jest wyjątek, który wydaje się działać dobrze, ale chciałbym czystsze podejście (może flagę mogę ustawić lub konkretną wartość mogę ustawić wartość zwracaną na), aby anulować GetOrAdd z wewnątrz lambda (choć może to być realistycznie zastąpione metodą pełnowartościową).

Na podstawie mojego doświadczenia z innymi metodami podobnymi do LINQ, zwracanie null spowoduje, że wartość zostanie dodana bez sprawdzania jako takiego (i przeczytanie IL dla GetOrAdd wygląda na to, że spowoduje to ten sam problem), więc nie myśl, że to zadziała.

Czy jest jakiś sposób uniknięcia wyjątków, aby anulować dodawanie za pomocą GetOrAdd?

Odpowiedz

10

Z tego, co przeczytałem, jest no guarantee that the Add factory method will only be called a single time amongst all callers to Get for the same key.

odpowiedniej części z tej stronie na dole, cytowane tutaj:

Również, chociaż wszystkie metody ConcurrentDictionary (z t w, TValue) są bezpieczne wątek nie wszystkie metody są niepodzielne, a zwłaszcza GetOrAdd i AddOrUpdate. Użytkownik przekazany do tych metod to wywoływany poza wewnętrzną blokadą słownika. (Ma to na celu zapobiec nieznany kod blokuje wszystkie wątki). Dlatego jest możliwość ta sekwencja zdarzeń występują:

1) threadA wzywa GetOrAdd, nie znajduje żadnego elementu i tworzy nowy element do dodania przez wywoływanie delegata valueFactory.

2) threadB wzywa GetOrAdd jednocześnie jego delegat valueFactory jest powoływać i przybywa na zamek wewnętrznego przed threadA, a więc jego nową parę klucz-wartość jest dodawana do słownika.

3) przenosić użytkownik threadA zakończy się, a nić przybywa na zamek , ale teraz widzi, że element już istnieje

4) threadA wykonuje „Get” i zwraca dane, które wcześniej dodanej przez threadB.

Dlatego nie można zagwarantować, że dane zwrócone przez GetOrAdd są tymi samymi danymi, które zostały utworzone przez wartość wątku: wartośćFactory. Podobna sekwencja zdarzeń może wystąpić po wywołaniu AddOrUpdate .

Sposób, w jaki to przeczytam, jest taki, że nawet jeśli wywołujesz pewne blokowanie w swoim delegacie dodawania, nie masz gwarancji, że wartość zwrócona z twojego dodania to ta, która zostanie użyta.

Więc nie ma potrzeby, aby dodać dalszego blokowania, a zamiast prawdopodobnie mógłby użyć następującego wzoru:

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

Tak, spowoduje to pewien potencjał dla nowych obiektów mają być utworzone potencjalnie wielokrotnie , ale wszyscy dzwoniący otrzymają taki sam wynik, nawet jeśli zostanie wywołanych wielu. To samo, co teraz GetOrAdd.

+0

To (i _ mam), ale gdy zagracie się to z wszystkimi niezbędnymi mechanizmami blokowania nici, staje się raczej brudny (nie, że mój nie wymaga blokowania ... tylko nie w tym samym stopniu). Szukam czystszego rozwiązania, a nie takiego, które wymaga efektywnej implementacji mojej własnej wersji 'ConcurrentDictionary'. –

+0

Zaktualizowałem odpowiedź - GetOrAdd nie blokuje części Add, tylko w przypadku wewnętrznych bitów tablicy. Tak więc, jak widzę, nie powinieneś tu potrzebować żadnych zamków, jeśli twoim zamiarem jest tylko dodać walidację. –

+0

Tak, przeczytałem tę część dokumentów, ale to wciąż nie jest to, czego szukam. Gdybym chciał napisać tak dużo kodu, sam sobie poradziłbym z blokadą. –

3

Wszystkie problemy w informatyce może być rozwiązany przez inny poziom zadnie

// 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 ... 
} 

Teraz wartość zapamiętana w słowniku jest funkcja; gdy nie chcesz, aby wystąpiło buforowanie, funkcja zawsze tworzy nowe wystąpienie; gdy chcesz, aby wystąpiło buforowanie, funkcja zwraca instancję z pamięci podręcznej.

+0

Czy na pewno to zadziała? Wszystko, co tutaj zrobiłeś, to przesunąć czek do funkcji i mimo to zwrócić instancję tego typu. Prawdopodobnie potrzeba więcej wyjaśnień. –

+0

Pewnie, dodam trochę więcej ... –

+0

Przeniesienie czeku do oddzielnej funkcji nie ma nic wspólnego z sercem pytania, to był tylko zwykły refaktoryzacji .... –