2013-04-29 13 views
7

Tak więc natknąłem się na interesujący problem polegający na tym, że otrzymuję zduplikowane klucze w słowniku C# przy użyciu klucza typu PhysicalAddress. Jest to interesujące, ponieważ dzieje się to dopiero po bardzo długim czasie i nie mogę go odtworzyć przy użyciu tego samego kodu w teście jednostkowym na zupełnie innej maszynie. Mogę go odtworzyć niezawodnie na komputerze z systemem Windows XP SP3, ale tylko po uruchomieniu go przez kilka dni, a nawet wtedy tylko raz.Zduplikowane klucze w słowniku podczas używania PhysicalAddress jako klucza

Poniżej znajduje się kod, którego używam, a pod nim jest wyjście dziennika dla tej części kodu.

Kod:

private void ProcessMessages() 
{ 
    IDictionary<PhysicalAddress, TagData> displayableTags = new Dictionary<PhysicalAddress, TagData>(); 

    while (true) 
    { 
     try 
     { 
      var message = incomingMessages.Take(cancellationToken.Token); 

      VipTagsDisappeared tagsDisappeared = message as VipTagsDisappeared; 

      if (message is VipTagsDisappeared) 
      { 
       foreach (var tag in tagDataRepository.GetFromTagReports(tagsDisappeared.Tags)) 
       { 
        log.DebugFormat(CultureInfo.InvariantCulture, "Lost tag {0}", tag); 

        RemoveTag(tag, displayableTags); 
       } 

       LogKeysAndValues(displayableTags); 

       PublishCurrentDisplayableTags(displayableTags); 
      } 
      else if (message is ClearAllTags) 
      { 
       displayableTags.Clear(); 
       eventAggregator.Publish(new TagReaderError()); 
      } 
      else if (message is VipTagsAppeared) 
      { 
       foreach (TagData tag in tagDataRepository.GetFromTagReports(message.Tags)) 
       { 
        log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag ({0}) with Exciter Id ({1})", tag.MacAddress, tag.ExciterId); 

        if (tagRules.IsTagRssiWithinThreshold(tag) && tagRules.IsTagExciterValid(tag)) 
        { 
         log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag is displayable ({0})", tag); 

         bool elementAlreadyExists = displayableTags.ContainsKey(tag.MacAddress); 

         if (elementAlreadyExists) 
         { 
          displayableTags[tag.MacAddress].Rssi = tag.Rssi; 
         } 
         else 
         { 
          displayableTags.Add(tag.MacAddress, tag); 
         } 
        } 
        else 
        { 
         log.DebugFormat(CultureInfo.InvariantCulture, "Detected tag is not displayable ({0})", tag); 

         RemoveTag(tag, displayableTags); 
        } 
       } 

       LogKeysAndValues(displayableTags); 

       PublishCurrentDisplayableTags(displayableTags); 
      } 
      else 
      { 
       log.WarnFormat(CultureInfo.InvariantCulture, "Received message of unknown type {0}.", message.GetType()); 
      } 
     } 
     catch (OperationCanceledException) 
     { 
      break; 
     } 
    } 
} 

private void PublishCurrentDisplayableTags(IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    eventAggregator.Publish(new CurrentDisplayableTags(displayableTags.Values.Distinct().ToList())); 
} 

private void RemoveTag(TagData tag, IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    displayableTags.Remove(tag.MacAddress); 

    // Now try to remove any duplicates and if there are then log it out 
    bool removalWasSuccesful = displayableTags.Remove(tag.MacAddress); 

    while (removalWasSuccesful) 
    { 
     log.WarnFormat(CultureInfo.InvariantCulture, "Duplicate tag removed from dictionary: {0}", tag.MacAddress); 
     removalWasSuccesful = displayableTags.Remove(tag.MacAddress); 
    } 
} 

private void LogKeysAndValues(IDictionary<PhysicalAddress, TagData> displayableTags) 
{ 
    log.TraceFormat(CultureInfo.InvariantCulture, "Keys"); 
    foreach (var physicalAddress in displayableTags.Keys) 
    { 
     log.TraceFormat(CultureInfo.InvariantCulture, "Address: {0}", physicalAddress); 
    } 

    log.TraceFormat(CultureInfo.InvariantCulture, "Values"); 
    foreach (TagData physicalAddress in displayableTags.Values) 
    { 
     log.TraceFormat(CultureInfo.InvariantCulture, "Address: {0} Name: {1}", physicalAddress.MacAddress, physicalAddress.Name); 
    } 
} 

i przetwarzanie wiadomości jest używany w następujący sposób:

Thread processingThread = new Thread(ProcessMessages); 

GetFromTagReports Kod

public IEnumerable<TagData> GetFromTagReports(IEnumerable<TagReport> tagReports) 
{ 
    foreach (var tagReport in tagReports) 
    { 
     TagData tagData = GetFromMacAddress(tagReport.MacAddress); 
     tagData.Rssi = tagReport.ReceivedSignalStrength; 
     tagData.ExciterId = tagReport.ExciterId; 
     tagData.MacAddress = tagReport.MacAddress; 
     tagData.Arrived = tagReport.TimeStamp; 

     yield return tagData; 
    } 
} 

public TagData GetFromMacAddress(PhysicalAddress macAddress) 
{ 
    TagId physicalAddressToTagId = TagId.Parse(macAddress); 

    var personEntity = personFinder.ByTagId(physicalAddressToTagId); 

    if (personEntity.Person != null && !(personEntity.Person is UnknownPerson)) 
    { 
     return new TagData(TagType.Person, personEntity.Person.Name); 
    } 

    var tagEntity = tagFinder.ByTagId(physicalAddressToTagId); 

    if (TagId.Invalid == tagEntity.Tag) 
    { 
     return TagData.CreateUnknownTagData(macAddress); 
    } 

    var equipmentEntity = equipmentFinder.ById(tagEntity.MineSuiteId); 

    if (equipmentEntity.Equipment != null && !(equipmentEntity.Equipment is UnknownEquipment)) 
    { 
     return new TagData(TagType.Vehicle, equipmentEntity.Equipment.Name); 
    } 

    return TagData.CreateUnknownTagData(macAddress); 
} 

Gdzie Adres fizyczny jest tworzony

var physicalAddressBytes = new byte[6]; 
ByteWriter.WriteBytesToBuffer(physicalAddressBytes, 0, protocolDataUnit.Payload, 4, 6); 

var args = new TagReport 
{ 
    Version = protocolDataUnit.Version, 
    MacAddress = new PhysicalAddress(physicalAddressBytes), 
    BatteryStatus = protocolDataUnit.Payload[10], 
    ReceivedSignalStrength = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(protocolDataUnit.Payload, 12)), 
    ExciterId = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(protocolDataUnit.Payload, 14)) 
}; 

public static void WriteBytesToBuffer(byte[] oldValues, int oldValuesStartindex, byte[] newValues, int newValuesStartindex, int max) 
{ 
    var loopmax = (max > newValues.Length || max < 0) ? newValues.Length : max; 

    for (int i = 0; i < loopmax; ++i) 
    { 
     oldValues[oldValuesStartindex + i] = newValues[newValuesStartindex + i]; 
    } 
} 

Uwaga następujące:

  • Każdego Tag "w messages.Tags zawiera 'nowy' PhysicalAddress.
  • Każda zwrócona wiadomość jest również "nowa".
  • Metody "tagRules" w żaden sposób nie modyfikują przekazanego w "tagu".
  • Poszczególne testy z próbą umieszczenia dwóch instancji PhysicalAddress (utworzonych z tych samych bajtów) w Słowniku powodują wyjątek "KeyAlreadyExists".
  • Próbowałem również TryGetValue i dało taki sam wynik.

wyjściowych dziennika, w którym wszystko było w porządku:

2013-04-26 18:28:34,347 [8] DEBUG ClassName - Detected tag (000CCC756081) with Exciter Id (0) 
2013-04-26 18:28:34,347 [8] DEBUG ClassName - Detected tag is displayable (Unknown: ?56081) 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Keys 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755898 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755A27 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755B47 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Values 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755898 Name: Scotty McTester 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755A27 Name: JDTest1 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Address: 000CCC755B47 Name: 33 1 
2013-04-26 18:28:34,347 [8] TRACE ClassName - Current tags: Scotty McTester, ?56081, JDTest1, 33 1 

wyjście Log gdzie możemy uzyskać duplikat klucza:

2013-04-26 18:28:35,608 [8] DEBUG ClassName - Detected tag (000CCC756081) with Exciter Id (0) 
2013-04-26 18:28:35,608 [8] DEBUG ClassName - Detected tag is displayable (Unknown: ?56081) 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Keys 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Address: 000CCC755898 
2013-04-26 18:28:35,608 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755A27 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755B47 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC756081 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Values 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC755898 Name: Scotty McTester 
2013-04-26 18:28:35,618 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC755A27 Name: JDTest1 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC755B47 Name: 33 1 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Address: 000CCC756081 Name: ?56081 
2013-04-26 18:28:35,648 [8] TRACE ClassName - Current tags: Scotty McTester, ?56081, JDTest1, 33 1, ?56081 

Zauważ, że wszystko dzieje się na jednym wątku (patrz [8 ]), więc nie ma szans na jednoczesne zmodyfikowanie słownika. Fragmenty pochodzą z tego samego dziennika i tej samej instancji procesu. Zauważ, że w drugim zestawie logów kończymy dwoma kluczami, które są takie same!

Na co patrzę: Zmieniłem adres fizyczny na ciąg, aby sprawdzić, czy mogę go wyeliminować z listy podejrzanych.

Moje pytania są następujące:

  • Czy istnieje problem, że nie widzę w kodzie powyżej?
  • Czy jest jakiś problem z metodami równości na adresie fizycznym? (To tylko błąd co jakiś czas?)
  • Czy jest jakiś problem ze Słownikiem?
+0

Możesz zauważyć, że nieczynny bieg nie dzieje się w tym samym czasie. Może to być argumentem za problematycznym wątkiem. Jak możesz być pewien, że 'displayableTags' nie jest obiektem wspólnym? Czy jest to zmienna lokalna? Własność? Ponadto użyj 'TryGetValue' zamiast' ContainsKey'. –

+0

Mogę być pewien, ponieważ "displayableTags" jest lokalnie utworzoną zmienną utworzoną w metodzie wywoływanej przez konstruktor wątków. Próbowałem TryGetValue i zrobiłem to samo (dodam to do pytania). Również z dokumentu msdn na TryGetValue: _Ta metoda łączy w sobie funkcjonalność metody ContainsKey i właściwości Item._ – JohnDRoach

+0

Czy możesz umieścić kod w jednym bloku? Problemem może być również Ty w twoich logach, widzimy to? –

Odpowiedz

9

Słownik oczekuje niezmiennego obiektu jako klucza, ze stabilną implementacją GetHashCode/Equals. Oznacza to, że po umieszczeniu obiektu w słowniku wartość zwracana przez GetHashCode powinna być zmieniona na , a wszelkie zmiany dokonane w tym obiekcie nie powinny wpływać na metodę Equals.

Mimo że klasa PhysicalAddress została zaprojektowana jako niezmienna, nadal zawiera kilka punktów rozszerzeń, , gdzie jej niezmienność jest wadliwa.

pierwsze, może ona zostać zmieniona poprzez tablicy bajtów wejściowych, który nie jest kopiowany ale przekazywane przez referencję, tak:

var data = new byte[] { 1,2,3 }; 
var mac = new PhysicalAddress(data); 
data[0] = 0; 

drugie, PhysicalAddress nie jest uszczelniony klasę i mogą być zmieniane przez pochodzi implementacja poprzez nadpisanie metod Constructor/GetHashCode/Equals. Ale ten przypadek wygląda bardziej jak włamanie, więc zignorujemy to, a także modyfikacje poprzez odbicie.

Twoja sytuacja może zostać osiągnięta tylko poprzez umieszczenie obiektu PhysicalAddress w słowniku, , a następnie zmodyfikowanie jego tablicy bajtów źródłowych, a następnie zawinięcie do nowej instancji PhysicalAddress.

szczęście realizacja GetHashCode PhysicalAddress' hash oblicza się tylko raz, a jeśli sama instancja jest modyfikowany, jest on nadal umieszczony w tym samym słowniku wiadro, i znajduje się ponownie przez równych sobie.

Ale, jeśli tablica źródło bajt jest przekazywana do innego wystąpienia PhysicalAddress mieszania, w którym nie był jeszcze obliczonego - mieszania jest wyliczany przez wytwarzany nowy bajt [] Wartość nowa wiadra jest usytuowany, i duplikatem włożona słownika. W rzadkich przypadkach to samo wiadro może znajdować się pod numerem z nowego hasha i ponownie nie jest wstawiany żaden duplikat.

Oto kod, który reprodukuje problem:

using System; 
using System.Collections.Generic; 
using System.Net.NetworkInformation; 

class App 
{ 
    static void Main() 
    { 
    var data = new byte[] { 1,2,3,4 }; 
    var mac1 = new PhysicalAddress(data); 
    var mac2 = new PhysicalAddress(data); 
    var dictionary = new Dictionary<PhysicalAddress,string>(); 
    dictionary[mac1] = "A"; 
    Console.WriteLine("Has mac1:" + dictionary.ContainsKey(mac1)); 
    //Console.WriteLine("Has mac2:" + dictionary.ContainsKey(mac2)); 
    data[0] = 0; 
    Console.WriteLine("After modification"); 
    Console.WriteLine("Has mac1:" + dictionary.ContainsKey(mac1)); 
    Console.WriteLine("Has mac2:" + dictionary.ContainsKey(mac2)); 

    dictionary[mac2] = "B"; 
    foreach (var kvp in dictionary) 
     Console.WriteLine(kvp.Key + "=" + kvp.Value); 
    } 
} 

Zanotuj skomentował linia - jeśli będziemy go odkomentowaniu „containsKey” metoda wstępnego wyliczenia mieszania dla MAC2, i będzie to samo, nawet po modyfikacji.

Tak więc, moim zaleceniem jest zlokalizowanie fragmentu kodu generującego instancje PhysicalAddress i utworzenie nowej tablicy tablic bajtowych dla każdego wywołania konstruktora.

+0

Dziękuję za dobrze skonstruowaną odpowiedź :) Niestety, już teraz tworzymy nową tablicę bajtów z każdym wywołaniem konstruktora. Zobacz kod ostatnio dodany do pytania. Właściwość MacAddress w TagReport nigdy nie jest do tego przypisana i jest używana tylko. Utworzona tam instancja w końcu sprawia, że ​​jest to droga do wywołania GetTagReports. – JohnDRoach

+0

Co dzieje się z fizycznymi adresami po utworzeniu TagReport? Czy jest gdzieś ponownie wykorzystany? Jaki jest średni rozmiar słownika? Jak często jest modyfikowany? – Alexander

+0

Niewiele więcej pomysłów - spróbuj przetestować pamięć serwera, na której możesz odtworzyć ten problem. Umieść wszystkie metody dostępu do słownika w lock(), aby się upewnić, że nie ma problemów z wielowątkowością. – Alexander

Powiązane problemy