2011-11-16 26 views
8

Ten wyjątek występuje w następującym bloku kodu w kontekście ASP.NET, który działa na serwerze IIS 7.Wyjątek podczas dodawania wpisu w słowniku

1) Exception Information 
********************************************* 
Exception Type: System.Exception 
Message: Exception Caught in Application_Error event 
Error in: InitializationStatus.aspx 
Error Message:An item with the same key has already been added. 
Stack Trace: at 
System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 
at CredentialsSession.GetXmlSerializer(Type serializerType) 

jest to kod, który jest wyjątek występujący w:

[Serializable()] 
public class CredentialsSession 
{ 
    private static Dictionary<string, System.Xml.Serialization.XmlSerializer> localSerializers = new Dictionary<string, XmlSerializer>(); 

    private System.Xml.Serialization.XmlSerializer GetXmlSerializer(Type serializerType) 
    { 
     string sessionObjectName = serializerType.ToString() + ".Serializer"; 

     if (Monitor.TryEnter(this)) 
     { 
      try 
      { 
       if (!localSerializers.ContainsKey(sessionObjectName)) 
       { 
        localSerializers.Add(sessionObjectName, CreateSerializer(serializerType)); 
       } 
      } 
      finally 
      { 
       Monitor.Exit(this); 
      } 
     } 
     return localSerializers[sessionObjectName]; 
    } 

    private System.Xml.Serialization.XmlSerializer CreateSerializer(Type serializerType) 
    { 
     XmlAttributes xmlAttributes = GetXmlOverrides(); 

     XmlAttributeOverrides xmlOverrides = new XmlAttributeOverrides(); 
     xmlOverrides.Add(typeof(ElementBase), "Elements", xmlAttributes); 

     System.Xml.Serialization.XmlSerializer serializer = 
      new System.Xml.Serialization.XmlSerializer(serializerType, xmlOverrides); 

     return serializer; 
    } 
} 

Monitor.TryEnter powinny być zapobieganie wielu wątków z wejściem bloku jednocześnie, a kod jest sprawdzenie słownikowi sprawdź, czy nie zawiera on klucza, który jest dodawany.

Jakieś pomysły na to, jak to może się stać?

+0

+1, za pytanie wyjaśniające, jak znaleźć duplikat w słowniku. –

Odpowiedz

5

Twój kod nie jest wątek bezpieczny.

  1. Ty blokowania na this, instancja CredentialsSession, ale dostęp do statycznego słownika, który może być współdzielony przez wielu CredentialsSession przypadkach. To wyjaśnia, dlaczego otrzymujesz błąd - dwie różne instancje CredentialsSession próbują jednocześnie pisać do słownika.

  2. Nawet jeśli zmienisz to, aby zablokować statyczne pole, jak zasugerowano w odpowiedzi @ sll, nie jesteś bezpieczny dla wątków, ponieważ nie blokujesz podczas czytania słownika. Potrzebujesz ReaderWriterLock lub ReaderWriterLockSlim, aby skutecznie umożliwić obsługę wielu czytników i jednego programu piszącego.

    Dlatego powinieneś używać słownika bezpiecznego dla wątków. ConcurrentDictionary, jak powiedzieli inni, jeśli używasz .NET 4.0. Jeśli nie, powinieneś wdrożyć własną lub użyć istniejącej implementacji, takiej jak http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx.

Twoje uwagi sugerują, że chcesz uniknąć wielokrotnego wywoływania numeru CreateSerializer dla tego samego typu. Nie wiem dlaczego, ponieważ korzyści związane z wydajnością mogą być znikome, ponieważ rywalizacja może być rzadkością i nie może przekroczyć jednego razu dla każdego typu w trakcie trwania aplikacji.

Ale jeśli naprawdę chcesz, to możesz to zrobić w następujący sposób:

var value; 
if (!dictionary.TryGetValue(key, out value)) 
{ 
    lock(dictionary) 
    { 
     if(!dictionary.TryGetValue(key, out value)) 
     { 
      value = CreateSerializer(...); 
      dictionary[key] = value; 
     } 
    } 
} 

Od Komentarz:

gdybym zaimplementować to z ConcurrentDictionary i po prostu zadzwonić TryAdd (sessionObjectName, CreateSerializer (serializerType)) każdego razu.

Odpowiedź nie polega na tym, aby wywołać metodę TryAdd za każdym razem - najpierw sprawdź, czy jest w słowniku, a następnie dodaj, jeśli nie jest. Lepszą alternatywą może być użycie argumentu the GetOrAdd overload, który przyjmuje argument Func.

+0

Nie zauważyłem, że słownik jest statyczny! To bardzo wyjaśnia, dzięki! Ponownie wdrażam to z ConcurrentDictionary. – Avalanchis

+0

Zakładając, że GetXmlSerializer jest często wywoływany w celu pobrania wartości ze Słownika, jeśli zaimplementuję to za pomocą ConcurrentDictionary i po prostu wywołaję TryAdd (sessionObjectName, CreateSerializer (serializerType)) za każdym razem, wtedy CreateSerializer będzie wywoływany za każdym razem, gdy wywoływany jest GetXmlSerializer. Wygląda na to, że chciałbym tego uniknąć. Po zapełnieniu słownika dla każdej wartości klucza, nie ma już powodu, aby wywoływać CreateSerializer.Poprawny? – Avalanchis

+2

+1, ponieważ ta odpowiedź w pełni opisuje istniejący problem i sugeruje, jak go rozwiązać w różnych przypadkach. – sll

5

Wypróbuj blokowanie na localSerializers raczej niż this. BTW, dlaczego używasz monitora jawnie? Tylko jeden z powodów, widzę jest zapewnienie lock timeout co oczywiście nie używasz, więc zamiast używać po prostu lock() statement będzie to generować try/finally, a także:

lock (localSerializers) 
{ 
    if (!localSerializers.ContainsKey(sessionObjectName))     
    {      
     localSerializers.Add(
      sessionObjectName, 
      CreateSerializer(serializerType));     
    } 
} 

EDIT: Ponieważ nie zostały określone w tagach że używasz .NET 4 Sugerowałbym użyciu ConcurrentDictionary<TKey, TValue>


Monitor.Enter() Method:

Użyj bloku C# try ... finally (Spróbuj ... Wreszcie w języku Visual Basic), aby upewnić się, że zwolniono monitor lub użyj instrukcji blokady C# (SyncLock w języku Visual Basic), która otacza metody Enter i Exit w języku ActionScript 3.0. try ... finally zablokować

+0

Dzięki za odpowiedź! Oryginalnie nie był to mój kod, więc nie mogę udzielić odpowiedzi na pytanie, dlaczego używa on monitora jawnie. Sensowne jest zablokowanie zasobu, który jest chroniony, a nie ten. Spróbuję tego, dzięki! – Avalanchis

+0

@Avalanchis: zobacz EDYTUJ część zaktualizowanej odpowiedzi, również dodałem tag .NET 4 dla twojego pytania, to jest całkiem ważne – sll

+0

Podoba mi się pomysł użycia ConcurrentDictionary, ale niepokoją mnie niepotrzebne połączenia z CreateSerializer. Wygląda na to, że nadal potrzebuję wywoływać ContainsKey, aby sprawdzić, czy klucz istnieje przed wywołaniem TryAdd, jeśli nie chcę za każdym razem wywoływać CreateSerializer. Poprawny? – Avalanchis

1

Jeśli jesteś na .NET Framework 4 lub później, chciałbym zaproponować, że używasz ConcurrentDictionary zamiast. Metoda TryAdd Chroni przed tego rodzaju scenariusza, bez konieczności miotu kod z zamkami:

localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType)) 

Jeśli martwisz się o CreateSerializer się powoływać, gdy nie jest to konieczne, należy zamiast tego użyć AddOrUpdate:

localSerializers.AddOrUpdate(
    sessionObjectName, 
    key => CreateSerialzer(serializerType), 
    (key, value) => value); 

Zapewni to, że metoda zostanie wywołana tylko wtedy, gdy trzeba będzie wygenerować nową wartość (gdy trzeba ją dodać do słownika). Jeśli jest już obecny, wpis zostanie "zaktualizowany" do już istniejącej wartości.

+0

Używamy .NET 4. Czy nadal byłoby sensowne sprawdzenie, czy ConcurrentDictionary zawiera klucz do dodania najpierw? Obawiam się, że TryAdd może niepotrzebnie wywoływać CreateSerializer, jeśli klucz już istnieje. – Avalanchis

+0

@Avalanchis: zobacz zaktualizowaną odpowiedź. –

Powiązane problemy