Co dodać do części aktualizacji w ConcurrentDictionary AddOrUpdate

109

Próbuję ponownie napisać kod za pomocą Dictionary, aby użyć ConcurrentDictionary. Przejrzałem kilka przykładów, ale nadal mam problemy z zaimplementowaniem funkcji AddOrUpdate. To jest oryginalny kod:

    dynamic a = HttpContext;
    Dictionary<int, string> userDic = this.HttpContext.Application["UserSessionList"] as Dictionary<int, String>;

   if (userDic != null)
   {
      if (useDic.ContainsKey(authUser.UserId))
      {
        userDic.Remove(authUser.UserId);
      }
   }
  else
  {
     userDic = new Dictionary<int,string>();
  }
  userDic.Add(authUser.UserId, a.Session.SessionID.ToString());
  this.HttpContext.Application["UserDic"] = userDic;

Nie wiem, co dodać do aktualizacji:

userDic.AddOrUpdate(authUser.UserId,
                    a.Session.SessionID.ToString(),
                    /*** what to add here? ***/);

Wszelkie wskazówki będą mile widziane.

user438331
źródło

Odpowiedzi:

220

Musisz przekazać a, Funcktóry zwraca wartość, która ma być przechowywana w słowniku w przypadku aktualizacji. Myślę, że w twoim przypadku (ponieważ nie rozróżniasz między dodawaniem a aktualizacją) byłoby to:

var sessionId = a.Session.SessionID.ToString();
userDic.AddOrUpdate(
  authUser.UserId,
  sessionId,
  (key, oldValue) => sessionId);

To znaczy, że Funczawsze zwraca sessionId, więc zarówno Add, jak i Update ustawiają tę samą wartość.

BTW: na stronie MSDN znajduje się przykład .

M4N
źródło
4
Poważnie walczyłem o znalezienie funkcji do dodania lub aktualizacji do tej samej wartości. thaks
Zapnologica
2
Dobra odpowiedź. Z samego podpisu AddOrUpdate () wyświetlanego w Visual Studio można tylko odgadnąć znaczenie dwóch parametrów. Jednak w konkretnym przypadku, o który pyta @ user438331, myślę, że rozwiązanie w mojej odpowiedzi z użyciem prostego indeksatora jest lepsze.
Niklas Peter
7
Jak wskazuje @NiklasPeter ( stackoverflow.com/a/32796165/8479 ), lepiej po prostu użyć normalnego indeksatora do nadpisania wartości, ponieważ w twoim przypadku nie interesuje Cię istniejąca wartość, jeśli taka istnieje. O wiele bardziej czytelny.
Rory
3
Poleciłbym zmienić swoją odpowiedź i skierować użytkowników do odpowiedzi @NiklasPeter. To znacznie lepsze rozwiązanie.
Will Calderwood
63

Mam nadzieję, że w Twoim pytaniu niczego mi nie brakowało, ale czemu nie tak po prostu? Jest łatwiejszy, atomowy i bezpieczny dla wątków (patrz poniżej).

userDic[authUser.UserId] = sessionId;

Przechowuj parę klucz / wartość bezwarunkowo w słowniku, nadpisując dowolną wartość dla tego klucza, jeśli klucz już istnieje: użyj metody ustawiającej indeksatora

(Patrz: http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx )

Indeksator też jest atomowy. Jeśli zamiast tego przekażesz funkcję, może to nie być:

Wszystkie te operacje są niepodzielne i są bezpieczne dla wątków w odniesieniu do wszystkich innych operacji w ConcurrentDictionary. Jedynym zastrzeżeniem co do atomowości każdej operacji są te, które akceptują delegata, a mianowicie AddOrUpdate i GetOrAdd. [...] ci delegaci są wywoływani poza śluzami

Zobacz: http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx

Niklas Peter
źródło
2
Tak atomowe, ponieważ dzieje się to w całości naraz i nie może wydarzyć się w połowie ani zostać przerwane. Jednak nie jest to bezpieczne w tym, że ktoś inny może zmienić to na coś innego tuż przed twoją czynnością, w którym to przypadku zmiana jest utracona i nie wiesz, że to się stało, jeśli chcesz ją zmienić tylko wtedy, gdy wartość jest taka, jakiej się wtedy spodziewasz to nie zrobi tego dla ciebie.
trampster
26

Skończyło się na wdrożeniu metody rozszerzenia:

static class ExtensionMethods
{
    // Either Add or overwrite
    public static void AddOrUpdate<K, V>(this ConcurrentDictionary<K, V> dictionary, K key, V value)
    {
        dictionary.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
    }
}
Steve Cook
źródło
1

Dla tych, którzy są zainteresowani, wdrażam obecnie przypadek, który jest świetnym przykładem użycia "starej wartości", czyli istniejącej wartości zamiast wymuszania nowej (osobiście nie podoba mi się termin "stara wartość", ponieważ to nie jest tak stary, kiedy został utworzony zaledwie kilka tyknięć procesora temu z równoległego wątku).

dictionaryCacheQueues.AddOrUpdate(
    uid,
    new ConcurrentQueue<T>(),
    (existingUid, existingValue) => existingValue
);
Nicolas
źródło
6
jeśli nie chcesz zmieniać istniejącej wartości, użyj GetOrAdd()zamiast tego msdn.microsoft.com/en-us/library/ee378674(v=vs.110).aspx
Rory
1
Hm tak, masz rację, GetOrAdd () jest prostsze iw tym przypadku wystarczające - dzięki za podpowiedź!
Nicolas